Creating Custom Elements

Overview

Dojo provides a powerful framework for build modern, reactive web applications. However there are times where it might be useful to use a widget you’ve created in another application written in a different framework (or no framework at all). This is where it may make sense to use a feature of Dojo, which is the ability to compile widgets to Custom Elements. Custom Elements allow developers to leverage a browser standard for defining their own HTML Elements. Dojo takes advantage of this by offering it as a compile target via the Dojo CLI, specifically using the @dojo/cli-build-widget command. Many other frameworks now support using Custom Elements, including Dojo; you can see support for various frameworks at www.custom-elements-everywhere.com.

Prerequisites

You can open the tutorial on codesandbox.io or download the demo project and run npm install to get started.

The @dojo/cli command line tool should be installed globally. Refer to the Dojo local installation article for more information. You must also have the @dojo/cli-build-widget command installed (npm install @dojo/cli-build-widget).

You also need to be familiar with TypeScript as Dojo uses it extensively.

Creating a Dojo Widget

Before we can generate a Custom Element, we must first create the Dojo Widget we wish to use. Here we will create a small widget with some properties and an event, and then show how we can use the @customElement decorator to allow the CLI to turn compile it to a Custom Element.

Examine the initial WorkerProfile widget in WorkerProfile.ts:

  1. import { WidgetBase } from '@dojo/framework/widget-core/WidgetBase';
  2. import { v } from '@dojo/framework/widget-core/d';
  3. import { theme, ThemedMixin } from '@dojo/framework/widget-core/mixins/Themed';
  4. import * as css from '../styles/worker.m.css';
  5. export interface WorkerProfileProperties {
  6. firstName?: string;
  7. lastName?: string;
  8. email?: string;
  9. timePerTask?: number;
  10. tasks?: string[];
  11. onSelected?: (data: any) => void;
  12. }
  13. @theme(css)
  14. export default class WorkerProfile extends ThemedMixin(WidgetBase)<WorkerProfileProperties> {
  15. private _onClick() {
  16. this.properties.onSelected && this.properties.onSelected(this.properties);
  17. }
  18. protected render() {
  19. const {
  20. firstName = 'firstName',
  21. lastName = 'lastName',
  22. email = 'unavailable',
  23. timePerTask = 0,
  24. tasks = []
  25. } = this.properties;
  26. return v('div', {
  27. classes: this.theme([ css.worker ]),
  28. onclick: this._onClick
  29. }, [
  30. v('img', {
  31. classes: this.theme(css.image),
  32. src: 'https://dojo.io/tutorials/resources/worker.svg'
  33. }),
  34. v('div', {
  35. classes: this.theme(css.generalInfo)
  36. }, [
  37. v('div', {
  38. classes : this.theme(css.label)
  39. }, ['Name']),
  40. v('div', [`${lastName}, ${firstName}`]),
  41. v('div', {
  42. classes: this.theme(css.label)
  43. }, ['Email']),
  44. v('div', [`${email}`]),
  45. timePerTask ? v('div', {
  46. classes: this.theme(css.label)
  47. }, ['Avg. Time per Task']) : undefined,
  48. timePerTask ? v('div', [`${timePerTask}`]) : undefined
  49. ]),
  50. tasks && tasks.length ? v('div', [
  51. v('strong', ['Current Tasks']),
  52. v('div', tasks.map(task => {
  53. return v('div', { classes: this.theme(css.task) }, [ task ]);
  54. }))
  55. ]) : undefined
  56. ]);
  57. }
  58. }

Here you can see a relatively simple widget that we will use to augment with the necessary code to allow the CLI to compile it to a Custom Element. In particular take a look at the properties that we are passing in, as we will need to reference these in when we go to state the properties and attributes of the Custom Element.

Applying the Custom Element Decorator

To allow @dojo/cli-build-widget to build the widget into a Custom Element, we need to augment the widget with the @customeElement decorator. The decorator takes an object, with a series of properties, in this case tag, events, attributes and properties.

Append the customElement decorator like so:

  1. @customElement<WorkerProfileProperties>({
  2. tag: 'worker-profile',
  3. events: ['onSelected'],
  4. attributes: [ 'firstName', 'lastName', 'email' ],
  5. properties: ['timePerTask', 'tasks']
  6. })
  7. @theme(css)
  8. export default class WorkerProfile extends ThemedMixin(WidgetBase)<WorkerProfileProperties> {

Here we have to give a tag property to the object passed to the decotrator. The tag is the HTML Element name we will use for the Custom Element. In this case the tag would be used like <worker-profile></worker-profile>. We have also let the CLI know how to map our properties and attributes correctly, as well as our events, using the respective properties in the decorator argument.

Here we ommit a couple of potential arguments from the above decorator demonstration for simplicity. However we will explain the other two for completeness. Firstly, there is childType. This property is relevant if you are interested in having children in the Custom Element. The property takes one of three arguments, namely DOJO (default), NODE or TEXT, each corresponding to what children the Custom Element accepts. DOJO will allow a Custom Element to render other Custom Elements created from Dojo Widgets. NODE means it will accept regular DOM Nodes, and TEXT means the element will accept plain text as a child. The next property is registry which accepts a given Registry chosen by the developer. Explaining registries in detail is outside the scope of this tutorial, but you can see the Registry tutorial for more information.

Creating a Custom Element with the Dojo CLI

Now to compile the Widget to a Custom Element. We do this via the command line, using the installed @dojo/cli-build-widget command.

Run the build custom element command like so:

dojo build widget —elements=src/widgets/WorkerProfile

Here we’ve called dojo build widget and passed it the path of the WorkerProfile TypeScript file to build it to a Custom Element. This Custom Element files will be outputted to a folder inside of output/dist/ called workerprofile. Here you will find the JavaScript, CSS and the source maps for the generated element.

Using the Created Custom Element

Now we have generated a the worker profile Custom Element, we can use it in other projects. As a basic example, lets show how that could be used in a blank web page. There are two ways to use a Custom Element, either declaratively (via HTML markup) or imperatively via JavaScript. Let’s take a look at both approaches.

The declarative approach can be done in the following manner:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Custom Element Demo</title>
  5. <link href="../output/dist/worker-profile/worker-profile-1.0.0.css" rel="stylesheet">
  6. </head>
  7. <body>
  8. <worker-profile firstName="Joe" secondName="Bloggs"></worker-profile>
  9. <script src='../output/dist/worker-profile/worker-profile-1.0.0.js'></script>
  10. </body>
  11. </html>

This works well, we can see that we import the JavaScript and CSS files for the Custom Element and then use it like we would any other HTML Element. However using the declarative approach means we can’t access Custom Element properties or events. If we go down the imperative route, we can set these accordingly.

You can use the outputted worker profile element in the following manner` like so:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Custom Element Demo</title>
  5. <link href="../output/dist/worker-profile/worker-profile-1.0.0.css" rel="stylesheet">
  6. </head>
  7. <body>
  8. <script src='../output/dist/worker-profile/worker-profile-1.0.0.js'></script>
  9. <script>
  10. var worker = document.createElement('worker-profile');
  11. worker.setAttribute('firstName', "Joe");
  12. worker.setAttribute('lastName', "Bloggs");
  13. worker.setAttribute('email', "[email protected]");
  14. worker.timePerTask = 100;
  15. worker.tasks = ['Being real busy'];
  16. worker.addEventListener('selected', (data) => { console.log(data.detail) })
  17. document.body.appendChild(worker);
  18. </script>
  19. </body>
  20. </html>

Once the JavaScript is imported, we use Custom Elements just like we would use other HTML Elements using standard DOM APIs like document.createElement, setAttribute and addEventListener (which is one of their strengths!).

Summary

In this tutorial we have shown how we can create a Dojo Widget, and then use the @customElement decorator to allow us to generate a Custom Element using @dojo/cli-create-widget. We have then demonstrated how you can use that Custom Element in another seperate page. Here, the ability to provide Custom Elements as an output from your widgets provides the benefit of having the ability to share code between different teams, projects and even companies that may be using other frameworks.