Revisiting Web Components with Dojo

odoenet

Rene Rubalcava

Posted on October 23, 2019

Revisiting Web Components with Dojo

In a previous post, we looked at how you could export Dojo widgets to Web Components. With the release of Dojo 6, this step is even easier. You no longer need to provide a decorator for your class based widgets, and even better, it works with function based widgets!

Widget

In our post on custom widget middleware, we created a geolocation widget. Here is what the middleware looked like.

// src/middleware/geolocation.ts
import { create } from "@dojo/framework/core/vdom";
import icache from "@dojo/framework/core/middleware/icache";

const factory = create({ icache });

type Coords = Pick<Coordinates, "latitude" | "longitude">;

// utility to get current geolocation
const getGeolocation = async (): Promise<Coords> => {
  return new Promise(resolve => {
    if (!("geolocation" in navigator)) {
      resolve({ latitude: 0, longitude: 0 });
    } else {
      navigator.geolocation.getCurrentPosition(({ coords }) => {
        const { latitude, longitude } = coords;
        resolve({ latitude, longitude });
      });
    }
  });
};

// default coordinates
const defaultCoordinates = { latitude: 0, longitude: 0 };

export const geolocation = factory(({ middleware: { icache } }) => {
  return (): Coords => {
    // get current value or default
    const coords = icache.getOrSet("coords", defaultCoordinates);
    if (coords.latitude === 0 && coords.longitude === 0) {
      // only get location if it is not the default
      getGeolocation().then(results => {
        if (
          coords.latitude !== results.latitude &&
          coords.longitude !== results.longitude
        ) {
          // only update cache if different from current value
          // this will invalidate the widget
          icache.set("coords", results);
        }
      });
    }
    return coords;
  };
});

export default geolocation;
Enter fullscreen mode Exit fullscreen mode

And here is a widget I'm going to write to use it.

// src/widgets/Geolocate.tsx
import { create, tsx } from "@dojo/framework/core/vdom";

import geolocation from "../middleware/geolocation";

import * as css from "./styles/Geolocate.m.css";

const factory = create({ geolocation });

const Geolocate = factory(function ({ middleware: { geolocation } }) {
  const { latitude, longitude } = geolocation();
  return (
    <div classes={[css.root]}>
      <span>
        <svg classes={[css.icon]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M15.5 0C10.806 0 7 1.817 7 6.543v16.901L15.5 32l8.5-8.556V6.543C24 1.817 20.194 0 15.5 0zm0 15.1a4.6 4.6 0 1 1 4.6-4.6 4.599 4.599 0 0 1-4.6 4.6z"/></svg>
      </span>
      <label>Lat: {latitude.toFixed(3)} | Lon: {longitude.toFixed(3)}</label>
    </div>
  );
});

export default Geolocate;
Enter fullscreen mode Exit fullscreen mode

This widget will render the current latitude and longitude based on the browsers Geolocation API.

This is what it should look like.

Alt Text

In order to export this widget as a web component, I only need to update the .dojorc to let the build tooling know what I want to do.

{
  "build-widget": {
    "prefix": "dojo",
    "widgets": [
      "src/widgets/Geolocate"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

When I run dojo build widget, this will create a web component, that I could use anywhere like this.

<!DOCTYPE html>
<html lang="en-us" dir="ltr">
<head>
    <meta charset="utf-8">
    <title>geolocate-widget</title>
    <meta name="theme-color" content="#222127">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="output/dist/geolocate-1.0.0.css">
    <script src="output/dist/geolocate-1.0.0.js"></script>
</head>
<body>
  <!-- use my web component -->
    <dojo-geolocate></dojo-geolocate>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Interactivity

Maybe I want to make this little widget and web component interactive. For example, I want to provide a property to make the text hidden and show it when I click on the icon of my widget. I can update my widget like this.

import { create, tsx } from "@dojo/framework/core/vdom";
// use the icache middleware
import icache from "@dojo/framework/core/middleware/icache";

import geolocation from "../middleware/geolocation";

import * as css from "./styles/Geolocate.m.css";

// provide an interface
interface GeolocateProperties {
  hidden?: boolean;
}

const factory = create({ geolocation, icache }).properties<GeolocateProperties>();

const Geolocate = factory(function ({ middleware: { geolocation, icache }, properties }) {
  // this will be a user provided property
  const { hidden } = properties();
  // this is the internal property based on the provided property
  const isHidden = icache.getOrSet('isHidden', hidden);

  const { latitude, longitude } = geolocation();
  // show or hide content based on given property
  const content = isHidden ? [] : <label>Lat: {latitude.toFixed(3)} | Lon: {longitude.toFixed(3)}</label>;
  return (
    <div classes={[css.root]}>
      <span classes={[css.span]} onclick={() => {
        icache.set('isHidden', !isHidden);
      }}>
        <svg classes={[css.icon]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M15.5 0C10.806 0 7 1.817 7 6.543v16.901L15.5 32l8.5-8.556V6.543C24 1.817 20.194 0 15.5 0zm0 15.1a4.6 4.6 0 1 1 4.6-4.6 4.599 4.599 0 0 1-4.6 4.6z"/></svg>
      </span>
      {content}
    </div>
  );
});

export default Geolocate;
Enter fullscreen mode Exit fullscreen mode

Here is a demo of what this widget looks like.

Once this is compiled into a web component, I can use it with the hidden property.

<body>
    <dojo-geolocate hidden></dojo-geolocate>
</body>
Enter fullscreen mode Exit fullscreen mode

You can find the source code for this widget on github.

Summary

I still think it's pretty useful to utilize Dojo widgets as a tool for building web components. In particular if you are interested in using some of the @dojo/widgets as web components in your own apps! Having the full capabilities of Dojo behind web components is very enticing!

💖 💪 🙅 🚩
odoenet
Rene Rubalcava

Posted on October 23, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Custom Dojo Middleware
dojo Custom Dojo Middleware

October 2, 2019