In the first part of this series, we introduced Web Components and gave you a sneak peek at some custom Nuxeo elements we are working on. We believe Web Components will radically change web development but for that to happen we need proper browser and tooling support as well as a set of best practices and frameworks to build upon.
In this post we will show you how you can start writing your own custom elements and your Web Components-based applications right away. We will introduce some of the existing polyfills and two of the major libraries available.
Let’s start by taking a look at one of the cornerstones of the Web Components specification, the one that allows us to register our own elements:
Custom Elements
Custom Elements let us define our own elements, associate JavaScript code with custom tag names, and then use those custom tag names as any standard tag. Native support for the W3C specification is only available in the latest versions of Chrome, Opera and to some extent Firefox, but by using a polyfill (https://github.com/Polymer/CustomElements) we can start using custom elements today in Safari, Opera, IE 9+, iOS Safari and Android 4+.
Polyfills
A polyfill is a javascript library that fixes issues with a browser’s API or adds interfaces that haven’t yet been implemented at all. If you remember our last post Web Components are formed by four specifications so we’ll be needing polyfills for some of these. Thankfully the teams working on the major Web Components frameworks (which we will take a look at next) have joined forces and have been working together on polyfills. This not only provides a good level of support but also ensures interoperability between elements developed with either framework!
So to start writing your custom elements all you need is to add the polyfill to your web page:
<script src="CustomElements/custom-elements.js">
Then you can register your custom element and associate it with a tag:
<document.registerElement('my-button, {
prototype: Object.create(HTMLElement.prototype)
});
After registering your custom element, you can create an instance of it just like you do for any other DOM element:
<my-button></my-button>
Our custom element doesn’t do much but you are free to add any functions or attributes to it and you can even extend existing elements:
<script>
// Our element’s prototype must be chained to the prototype we’re extending
var MyButton = Object.create(HTMLButtonElement.prototype);
// We can add functions to our prototype
MyButton.scream = function() {
this.style.fontSize = this.attributes.loudness.value + 'px';
};
// We register our custom tag
document.registerElement('my-button', {
prototype: MyButton,
extends: 'button'
});
</script>
<!--Since our element derives from an existing DOM element we must use the ‘is’ syntax-->
<button is="my-button" onclick="event.target.scream()"
loudness="25">Scream</button>
Note: When using extends to create a custom element that derives from an existing DOM element we must use the _is_ syntax and not the element’s tag name directly.
As you can see it’s really simple to start creating your own custom elements - and to make it even easier we’ve made the source code for the examples in this post available here.
So, by now you’re probably wondering why you need a framework and what additional value could it brings. So let’s take a look at the first of these frameworks:
X-Tag
X-Tag is created and supported by Mozilla and offers a very minimalist approach to bringing Custom Elements capabilities to all modern browsers. In fact, it doesn’t even rely on any of the other Web Components specifications as the Custom Elements API proves to be enough to build web components. This doesn’t mean that you can’t use other Web Components features like Shadow DOM or Templates, but you don’t have to.
It offers an API very close to the actual custom element’s API and builds on top of it to provide several powerful features that streamline element creation such as custom events, delegation, mixins, accessors, component lifecycle functions, pseudos and more.
Here’s how our sample button would be created using X-Tag:
xtag.register('my-button', {
extends: 'button',
methods: {
scream: function() {
this.style.fontSize = this.attributes.loudness.value + 'px';
},
},
events: {
click: function() {
this.scream();
}
}
});
So, X-Tag gives us a lean approach to building custom elements and allows us to opt-in for the additional technologies that make up the whole Web Components specification resulting in a smaller codebase.
However, when you do want to use all the bits and pieces, you’ll probably prefer to use something that follows a best practice approach and helps you combine these into a seamless experience. That’s where our next framework comes in.
Polymer
Polymer presents itself as a new type of library for the web built on top of Web Components. It uses a layered architecture with a set of new web technologies as its foundation. If you take a closer look at this foundation layer you will recognize the standards that form Web Components.
Polymer bundles polyfills for all these technologies in a library called platform.js and also provides support for Model-driven Views (MDV) by leveraging HTML Templates and some new ES6 and DOM standards like Mutation Observers and Object.observe, which enable watching for changes in the DOM and on JS objects respectively.
Before giving you an idea of the main differences between X-Tag and Polymer let’s see how our sample would look if built using Polymer:
<polymer-element name="my-button" extends="button"
attributes="loudness"
on-click="{{scream}}">
<template>Scream</template>
<script>
Polymer('my-button', {
loudness: 25,
scream: function() {
this.style.fontSize = this.loudness + 'px';
}
});
</script>
</polymer-element>
The first thing we notice is that Polymer provides a declarative way of registering custom elements by using
This is reminiscent of a proposed
Polymer’s platform also provides a polyfill for HTML imports, also part of the Web Components cast, thus it allows you to package and include HTML documents in other HTML documents so you can also include Custom Element definitions from external URLs just by using:
<link rel="import" href="my-button.html">
<button is="my-button"></button>
Polymer fills in the gaps between these technologies and provides a seamless way to bring them together in a very clean and simple way.
We said before that it builds on top of these, so let’s add some logic to our custom element to illustrate some of the features Polymer provides:
<polymer-element name="my-button" extends="button"
on click="{{toggle}}">
<template>
{{(isQuiet)?"Scream":"Quiet"}}
</template>
<script>
Polymer('my-button',{
isQuiet: true,
toggle: function(){
this.isQuiet = !this.isQuiet;
}
isQuietChanged: function(){
this.style.fontSize = {this.isQuiet)?'14px': '25px':
}
});
</script>
</polymer-element><script></script>
This simple example illustrates some of the extra sugaring that Polymer provides. The first thing you notice is the use of expressions surrounded by double-mustache {{ }} symbols. These are Polymer expressions and they create data bindings between our view, the template, and the model (in Polymer the model is always the element itself).
Updates to the model are reflected in the DOM and user input into the DOM is immediately assigned to the model. Thanks to Polymer’s support for two-way data binding we get proper separation between the UI (DOM) of an application and its underlying data (model). It’s a great way to quickly propagate changes and reduce boilerplate code.
Another feature that’s used in this example is the ability to observe properties. isQuietChanged is automatically invoked when our isQuiet property changes. Polymer allows registering changed watchers on all properties of an element simply by implementing a _propertyName_Changed handler.
Polymer brings a lot of value if you are building a Web Components-based application. We’ve only scratched the surface of its features but it really isn’t like other frameworks or libraries you might have used before. We highly recommend you to take a look at the project page to learn more about its declarative approach to creating custom elements with dynamic templates, two-way data binding and rich expressions, inheritance and event handling, property publishing and observation and several other features that were built based on their “Everything is an element” philosophy.
What’s next?
In the next part of this series we will take a look at the first set of Nuxeo Elements we are building.