Saturday, April 26, 2014

How to Create Custom HTML Elements

An exciting feature of the HTML specification that's been getting a bit of hype recently is custom HTML elements. These allow you to create your own HTML elements along with their own JavaScript API. This can be useful when building interfaces with components that are reused throughout an application.
In this blog post you're going to learn how to create your own HTML elements and define a JavaScript API for them.

Let's get started.

Creating Custom HTML Elements

The document.registerElement() method is used to create a custom HTML element. This should be passed as the name of your custom element along with an (optional) object that defines the API.

In the following example we simply create a new HTML element called <x-treehouse> and then add it to the page.

The name of your custom element must contain a dash (-) so that the browsers parser can determine between standard and custom HTML elements. This also means that you won't encounter problems if a new HTML element is introduced that uses the same name as your custom element.

Creating a JavaScript API For Your Element

You can define a JavaScript API for your custom element that consists of a number of methods and properties. To do this, start by creating a new JavaScript object. This can be done using the Object.create() method. Passing HTMLElement.prototype to this method will create an object with the standard set of methods and properties available to HTML elements.

var XTreehouseProto = Object.create(HTMLElement.prototype);

You can then define your custom methods on this new object, as shown below.

XTreehouseProto.hello = function() {
alert('Hello!');
}

To define a property for your custom element you can use the Object.defineProperty() method. The first parameter should be your prototype object; the second is the name of the property; and the third should be an object describing the behaviour of that property. This is where you can set a default value as well as specify whether the property is writable or read-only.

Once you've defined the API for your custom element, you need to call document.registerElement(). Use the name of your custom element as the first parameter and then pass in an object with a property named prototype. The value of this property should be set to the prototype object you created earlier.

The methods and properties you defined earlier can be accessed just as you would on any other HTML element.

xtreehouse.hello();
var badges = xtreehouse.badges;

Extending Existing Elements

As well as creating your own custom elements, you can also use the registerElement() method to extend the functionality of existing HTML elements. Let's extend the <img> element to create a variation for displaying thumbnail images.

You start by creating a prototype object as we did before. This time, however, you want to copy the prototype object of the element you are extending. In this case, that will be the HTMLImageElement.

var ThumbImageProto = Object.create(HTMLImageElement.prototype);

Next you define a function for the createdCallback, which is fired when the element is created. Here we can set the width and height of the image.

When extending an existing element, you need to add the extends property to your options object in the call to document.registerElement(). This property should be set to the name of the element you are extending.

To use your custom element, you can now specify an is attribute on the element that you have extended. Setting the value of this attribute to the name of your custom element will tell the browser that this <img> element should use the API defined for thumb-img.

<img is="thumb-img">

Custom Element Callback Methods

There are a number of callbacks that you can listen for when creating and managing your custom elements.

createdCallback – Called when a custom element is created.

attachedCallback – Called when a custom element is inserted into the DOM.

detachedCallback – Called when a custom element is removed from the DOM.

attributeChangedCallback(attrName, oldValue, newValue) – Called when an attribute on a custom element changes.

You specify functions for these callbacks on the prototype object that's passed to document.registerElement().

In this section we're going to look at an example of how you can use custom elements and Shadow DOM to create an interface component for displaying products in a web store. The idea here is that a web developer can easily create new products by adding a single line of HTML to their markup. All of the information needed to display the product is contained within data- attributes on the custom element.

We'll start by create a new prototype object based off of HTMLElement.prototype.

// Create a new object based of the HTMLElement prototype
var XProductProto = Object.create(HTMLElement.prototype);

Next we need to set up a function for createdCallback. This is where we will create the <img> and <a> elements that are responsible for displaying the product. I'll show you the code first and then walk you through it.

Here we start by creating a new Shadow Root. We then create an <img> element and set its alt, src, height and width attributes using the information specified on the x-product element.

Note: Inside the callback function, this refers to the custom element in your markup.

Next we add the <img> element to the shadow root and create a new <a> element. Again we set the attributes on the element using information from the data- attributes on the custom element. To finish up we add the <a> element we just created to the shadow root.

Now we need to register the custom element. Call document.registerElement() passing in x-product as the element name and specifying the XProductProto object as the prototype.

To display a product, we just need to add an <x-product> element to the HTML markup. The product data is set using the data-name, data-img and data-url attributes. When the page loads, the browser will recognise these as custom elements and fire the createdCallback event for each of them.

Note: This demo requires Shadow DOM, which is only supported in Chrome Canary.

Browser Support for Custom HTML Elements

Google Chrome (version 33+) and Opera are the only browsers with support for custom elements at the moment. However, there is a great polyfill available from the Polymer project that will add support for custom elements to other browsers. The x-tags polyfill maintained by Mozilla is also very useful.