Creating a web-component: VanillaJS vs X-Tag vs Polymer
It seems like these days the most trending concept in the HTML5 world is web components. But what the fuss is all about? In order to make such a simple thing as a container expanding on click in past we needed to write CSS and JavaScript. Today thanks to checkbox-hack and :focus or :target pseudo-selector we go on with CSS-only. In fact by using summary
/details
elements we do not need any tricks with CSS to make it working, considering that browser already supports this feature. Thus by placing this elements in the DOM we declare specific functionality, CSS is used for styling and JavaScript if still used only for extending the element basic behavior. That is how it is meant initially. However HTML brings just a few new elements what is hardly enough to replace all the custom plugins, libraries and snippets we use. But what if customs elements could be created by anybody? If source code, styles and markup could be kept encapsulated in a package? If the package could be included into HTML document by a link? It sounds almost like an industrial revolution. Just imagine that you need a slideshow on your page. So you add a link to a corresponding component and simply wrap your image slides with custom img-slider
element. That’s it, you have a running slideshow. You want something else? Just search in the web-components repository. In the other hand you can switch to a UI library based on web components, e.g. Mozilla Brick and build the entire application with web components Exciting, isn’t it?
That is in theory. I never have a grip on a new thing until I try it in practice. So I invite you to learn web components with me in practice.
The idea of component
XML element x-link was designed to set semantic relations between distinct documents. Using this element one can refer to a fragment of an external resource. There is no such element in HTML though. To refer out of the document we use simple links. So by clicking on one you get redirected to an external resource or moved within open document. I would like to have an option that shows a modal window and loads in there a relevant fragment of a document. I wonder of a custom element extending a(nchor) that provides this functionality. Let’s say we have an element x-reference that by a click reads the specified URL and shows in a modal window the content of the external document labeled with id some-id
;
<x-reference href="url" locator="#some-id"></x-reference>
Alternatively the intended fragment can be defined with a regular expression:
<x-reference href="url" grep="(Lorem.+nulla\.)"></x-reference>
By clicking on the element we get the following view:
Old-school solution would be JavaScript code that queries for x-reference as soon as DOM content is loaded and subscribes a handler for click event. The handler reads from url and extracts the fragment according to the selector in locator or to the condition in grep. Surely XMLHttpRequest will be able to reach external resource only when keeping to CORS.
Sources: https://github.com/dsheiko/x-reference/tree/master/non-component
As you see the sources (CSS and JavaScript) aren’t bundled and x-reference isn’t registered as a custom element. It is a unknown element though, what can be a problem. Besides we cannot expose any API through this elements. To make a web component we need the following HTML5 features: Custom elements, HTML Imports, Templates, Shadow DOM. At the moment they are poorly implemented in browsers. What can help is a set of polyfills called platform.js of Polymer project. The best way to install it – by using bower:
bower install --save Polymer/platform
When it is available it can be included into the HTML document:
<script src="bower_components/platform/platform.js"></script>
Cool, we have the polyfill and we can start on the web component.
VanillaJS
First we take Element Boilerplate and extract content to an arbitrary directory and download dependencies (platform.js)
bower install
Then we create a sub-directory called src
We bundle our CSS and JavaScript into ./src/x-reference.html and remove from our previously made JavaScript code window listener for DOMContentLoaded event. Instead we create a new element instance there:
element = Object.create( HTMLElement.prototype );
We set up a callback function that is called as soon as our custom element created in the DOM of host document.
element.createdCallback = function() {
this.xreference = new xReferenceView( this );
this.addEventListener( "click", this.xreference.handleOnClick.bind( this.xreference ), false );
};
This function creates an instance of xReferenceView and registers a handler for click event.
Custom element may have own properties and methods. So we expose a public method:
element.openModal = function(){
this.xreference.openModal();
};
At the end we register our element instance as a custom element.
document.registerElement('x-reference', {
prototype: element
});
Now we can create ./demo sub-directory and move there index.html of the boilerplate package. Then we add dummy-ext-resource.html that represents an external source.
We edit this file to modify the links to platform.js and to the custom element bundle (x-reference.html).
<!-- Importing Web Component's Polyfill -->
<script src="../bower_components/platform/platform.js"></script>
<!-- Importing Custom Elements -->
<link rel="import" href="../src/x-reference.html">
We also replace initial usage example code with our own.
Checking general behaviour of the element: We are expected to obtain external resource fragment
<x-reference href="./dummy-ext-resource.html?rel=author%26" locator="#cite">
<!-- Fallback -->
<a target="_blank" href="./dummy-ext-resource.html?rel=author">by locator</a>
</x-reference>
or
<x-reference href="./dummy-ext-resource.html?rel=author%26" grep="(Lorem.+nulla\.)">
<!-- Fallback -->
<a target="_blank" href="./dummy-ext-resource.html?rel=author">by grep</a>
</x-reference>
Voilà! It’s ready. But is the exposed method openModal really available? To check it we add into ./demo/index.html a test trigger link:
<a href="#" id="trigger">link</a>
and make it calling our method on click event:
<script>
(function(){
document.getElementById( "trigger" ).addEventListener( "click", function( e ){
e.preventDefault();
document.querySelector( "x-reference" ).openModal();
}, false );
}());
</script>
Sources: https://github.com/dsheiko/x-reference/tree/master/vanilla
X-Tag
Do you find that custom element creation API too primitive? Fortunately Mozilla presented a JavaScript library X-Tag that considerably improves the API. To try it we take the corresponding boilerplate and go through the steps as we did for VanillaJS example. However now we need to include into ./scr/x-reference.html x-tag core library:
<!-- Import X-Tag -->
<script src="../bower_components/x-tag-core/src/core.js"></script>
it allows us to register the custom elements like that:
xtag.register( "x-reference", {
lifecycle: {
created: function() {
this.xtag.xreference = new xReferenceView( this );
}
},
events: {
'click': function( e ){
this.xtag.xreference.handleOnClick( e );
}
},
methods: {
'openModal': function(){
this.xtag.xreference.openModal();
}
}
});
Sources: https://github.com/dsheiko/x-reference/tree/master/x-tag
Skate
I ran into a similar to X-Tag light-weight library Skate that also provides a comprehensive API based on the Custom Element spec. Unfurtunatelly it seems as does’t work with platform.js polyfills. Yet, you can find usage example at
https://github.com/dsheiko/x-reference/tree/master/skate|github.com/dsheiko/x-reference/tree/master/skate
Polymer
As I pointed above platform.js released under Google’s Polymer hood. We were using so far its build shiming only the essential technologies included in web component spec. The platform layer of Polymer provides must more: WeakMap, Mutation Observers, observe-js, Web Animations extended API. Besides the project includes collections of ready web components: core elements (UX components) and paper elements (layout structural components). Yeah, it looks worth studying.
As in previous examples we start with the boilerplate. In fact everything stays the same, except some changes to component bundle ( ./src/reference.html). We don’t need to implement modal window functionality customly as Polimer provides it already in core element overlay
. We simply import the component and its dependency:
<link rel="import" href="../bower_components/core-transition/core-transition-css.html">
<link rel="import" href="../bower_components/core-overlay/core-overlay.html">
Next we adapt our initial modal styles to this component. Following Polymer docs we wrap the component markup and JavaScript with special polymer-element tag:
<polymer-element name="x-reference">
..
</polymer-element>
In our case we don’t need such Polymer goodies as templating, two-way binding, but to have the custom element in host document DOM unchanged we need to add to the bundle an empty template:
<template>
<content></content>
</template>
The JavaScript section registering element in the Polymer example will look like that:
Polymer( "x-reference", {
created: function () {
this.xreference = new xReferenceView( this );
this.addEventListener( "click", this.xreference.handleOnClick.bind( this.xreference ), false );
},
openModal: function() {
this.xreference.openModal();
}
});
Sources: https://www.github.com/dsheiko/x-reference/tree/master/polymer
Recap
Instead of being bound to a heavy UX library like jQuery UI, DojoToolkit, Sencha Ext JS we can simply enable any web components that we like and use them as easy as any other HTML element. However it comes at a cost. Every web component bundle produces at least one HTTP request. Advanced components such as of Polymer are used to have plenty of dependencies. Thus we get proliferating HTTP requests during page loading what considerably increases user response time and therefore harms user experience. See below the network request list during Polymer example page loading: