DEV Community

Ivan
Ivan

Posted on • Edited on

Easy apps with hyperHTML — 5, Custom elements

Version en español

Part 5 written by

  1. Introduction, wire/bind
  2. Events and components
  3. Moar about components and simple state management
  4. Wire types and custom definitions (intents)
  5. Custom elements with hyper
  6. Customizing my custom elements
  7. Testing!
  8. Async loading, placeholder and a Typeahead with hyper
  9. Handling routes
  10. 3rd party libraries

My very own tag

If you’ve been developing web pages or apps for any amount of time, chances are you’ve seen custom components or HTML tags, especially if you’ve worked with one of the newer JavaScript frameworks. Whatever the name, these components usually all have a similar goal: encapsulate functions and logic into re-usable chunks of code. After a lot of discussion and development by the community, we finally brought these ideas under the canopy of Web Components.

Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps. — MDN

In order to achieve this, the specification addresses the following technologies:

  1. Custom elements : A set of JavaScript APIs that lets you define the logic of the element. You can create a function like open() and then you can just call that function from the element document.querySelector(‘custom-dialog’).open().
  2. Shadow Dom : Utilizing the shadow DOM enables you to use other HTML elements inside your custom element, even other custom elements! Whatever code is inside the custom element with shadow DOM enabled will be rendered and styled separately. This can be both a bug and a feature, depending on your use case.
  3. HTML templates : Two tags <template> and <slot> let you write HTML inside the custom element, but they are not rendered or displayed until you activate them. <template> and <slot> can also be re-used for other things.

Not every one of these are ready to use in all browsers, and certain aspects are harder to polyfill than others, but we are getting close.

For now we will concentrate on the basics of custom elements, but at the end we’ll show you how to use the shadow DOM. We’ll also see that hyperHTML can create highly efficient templates. We’ll be able to do everything with hyperHTML that <template> can, except for native use of <slot>.

<custom-element awesome=”true”>

To get started making custom elements, we need need 3 things:

  1. A browser that supports custom elements, or a polyfill. We recommend using document-register-element. This polyfill has been used in production for a while by projects like amp. Alternatively, Google also has a polyfill worth checking out.
  2. A class that extends the HTMLElement object. This class will contain all the logic for our element. We can also extend from a normal HTML element.
  3. Finally, we will need to “define” the element. This is how we’ll register our element in the browser, and give it a name.

In 2 above, we mentioned that there are two ways to extend your custom element class, either by extending HTMLElement, or a build-in HTML tag like span. When we extend HTMLElement, our custom element will be autonomous. However, extending built-ins is a bit controversial, and while the specs describe how to do it, not all browsers are on board. We will see how do both, but for our use case in this blog we are only going to extend from HTMLElement.

Enough jibber-jabber, show me the code!

(A tiny, huge thing in the index.html, since Stackblitz is automagically transpiling our code, we need to include the es5-adapter)

Lets dissect the code a bit:

First we name our class and extend HTMLElement. This will be an autonomous element. Then, we have a function called connectedCallback(). connectedCallback() will be called after the browser has appended the custom element to the document. We’ll talk more about the custom element lifecycle later on. Inside connectedCallback() we are just adding text to our custom element.

On line 9 we are defining our element name. Whatever name you choose, you must include a hyphen. In our case, to use this element in HTML we will use the tag <custom-element></custom-element>. The define function also takes a second parameter which is the class describing the element.

Internally, the browser keeps a registry of all the custom elements we define. One of the things we can do with the CustomElementRegistry is wait for the elements to be defined. However, we can do a lot of other things. So be sure to take a look at the docs.

Just for funz lets see how to extend a built-in:

Life cycle

Besides connectedCallback() there are other lifecycle hooks we can use. Let’s see what they do:

  • connectedCallback : called when your element is connected to the document DOM.
  • disconnectedCallback : called when your element is detached from the DOM. You will be able to react to that disconnection in this function.
  • adoptedCallback : is called when you move the element to a new page.
  • attributeChangedCallback : this is called whenever attributes that you are monitoring change. Not every attribute on your custom element will automatically be monitored. To pick which attributes are, you will have to add another function:
static get observedAttributes() {return ['attr1', 'attr2']; }
Enter fullscreen mode Exit fullscreen mode

Let’s see these functions in action in our custom-element:

This is cool! We’re well on our way to having a powerful new element. But what about templating? With hyperHTML, we can render whatever we want inside the element and boom, custom elements with all the power of hyperHTML :D

Let’s make a few changes. Of course, we first need to load hyperHTML. Then we will bind to the current element with this.html = bind(this); We also need a render() function which will be charged with rendering the template. And finally, we need to update our attributeChangedCallback() to change new attribute values to actual properties:

Enter hyperHTML-element

There is a lot more to learn about custom elements, but fortunately the master Andrea Giammarchi created another utility that abstracts much of the custom element API. It’s called hyperHTML-element.

This is a class that you can extend instead of the HTMLElement, and it gives you a few new functions and properties.

Functions

  • Besides the regular observedAttributes we now also have booleanAttributes. This is where we’ll want to put the attributes that don’t need an actual value. For example, <custom-element required>. “Required” either exists or it doesn’t, so it is a great candidate for being a boolean attribute.
  • observedAttributes and booleanAttributes both will also define getters and setters for every attribute observed. This means we no longer need to do this[name] = newValue inside attributeChangedCallback.
  • created. This is a new callback that will be invoked right before connectedCallback or attributeChangedCallback
  • defaultState(). Whenever you use this.state, the defaultState() function returns an object that becomes, you guessed it, the default state.
  • setState. Updates the state object (you don’t need to define defaultState to use setState). setState includes a little bonus: it will call render() for you, so your element is automatically updated when you change its state!
  • render(). This function should return the HTML you want to display inside your custom element.
  • Event handlers. In your template, we can now pass ‘this’ to our event handlers: <div onclick=${this}></div>. If we then define an onclick function inside the element, it will get called with the proper context. You can also use data-call to call any function: data-call=onAnyEvent onclick=${this}… in this case onAnyEvent will be called when a user clicks. Even more cool, custom events are super easy: onMyCustomEvent=${this} and you define a onMyCustomEvent(e) function, done!
  • define(). This static method is just a helper. Instead of using customElements.define you use this one MyElement.define('my-element')
  • All the hyperHTML functions, bind, component and wire. The only hyper function we don’t get is define for intents, since we’re already using it to register the element. To define intents you will use HyperHTMLElement.intent(…). We looked at intents in another part 4 of this blog, so be sure to brush up on them if you need to.

Observant readers will notice some of these functions are used in hyper.Component. Hopefully you are familiar with some of them now :D

Properties

  • this.html. In our example above we did this.html = bind(this); in the constructor. With hyperHTML-elemet we now can use this.html directly.
  • state. State is an object that holds the…er…state of the element. It’s empty by default, or it will hold whatever object is returned by defaultState, if you’ve defined that function.
  • As mention before, every attribute in observedAttributes and booleanAttributes will have its getter and setter, so you can use this.attr and the value will reflect the attribute. For example, <my-element parent="your mom"/>, using this.parent will give you the value, “your mom.”

Sweet! Let’s update our previous element to use all of these new features:

What about the shadow DOM?… oh yeah, it’s super easy:

Check line 8 this.attachShadow({mode: ‘open’});, if you inspect the element you’ll see that the shadow DOM is there!

There is more to learn about custom elements and web components in general. In the next part we will write some custom logic and update our table from part 4 to be a custom element. And as always, feedback is welcome!


Top comments (0)