Web Components
When it comes to the functioning principles and technologies used for development of web applications, significant changes have occurred over the past few years. One of these important trends is the move away from standard template systems on the client side, and the usage of mechanisms for the display and manipulation of the elements that HTML offers. Many component models have been developed in the form of independent libraries, but also as a part of the JavaScript framework. Here are some of the best known front-end solutions:
- AngularJS Directives (link is external)
- React (link is external)
- Polymer (link is external)
- Riot (link is external)
- Vue (link is external)
- Web Components (link is external)
Component models primarily manipulate the DOM, which is one of the most expensive operations. As a result, they are one of the most important parts of web applications.
What are Web Components?
Web components are a collection of standards or technologies that allow for the creation of HTML elements and their use within web pages and applications. They are part of the W3C specifications, meaning they represent a standard for the implementation of new elements.
Here is a simple example of an element that displays text a limited length (text-length) and adds an ellipsis at the end of the sentence if the text is longer than defined by the author.
HTML
<text-truncation text-length="100">
Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Cras ultricies ligula sed magna dictum porta. Quisque velit nisi, pretium ut lacinia in, elementum id enim. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus.
</text-truncation>
The goal of web components is to make the creation and use of these libraries and functionalities easier.
I'm sure that most of us have experienced a problem with adding a new library that contains several resources into the project, where each of those resources should've been connected to a main document within which we’d like to present a new functionality.
Here are 5 steps for standard implementation of a library:
- First, you should enter styles.
- Then, you should enter JavaScript, with a minimum of one file if the library is independent from other libraries.
- Often, libraries have certain images that should be available to JavaScript and styles.
- After all of that, you should set the HTML structure of the component.
- Finally, the component should be initialized by executing defined methods.
- Entering HTML file into the header of a document.
- Setting up HTML structure where we want to show the component.
Which technologies do Web Components use?
As mentioned previously, web components are made up of several technologies:- Custom elements
- HTML imports
- Templates
- Shadow DOM
Custom elements
Similar to how we have some defined HTML elements, we are now able to create our own custom elements and use them on other projects. We can define the name, attributes and content of new elements, by which we are in fact communicating with API of the elements.
Example:
HTML
<my-element attribute="attr-value">
..content..
</my-element>
When you define elements, it would be advisable to use a dash in the name in order to decrease the possibility of overlapping names with other elements. Also, it is necessary for the names of elements and their attributes to reflect their meaning and purpose.
HTML imports
This is the part of web components that enables the upload of all the necessary resources of a library (CSS, JS, images etc) with the inclusion of just a single HTML file. Implementation is performed by simply adding a tag in the header of a document, with an import for the value of rel attribute and a defining path to the HTML file.Example:
HTML
component.html
<link rel="stylesheet" href="css/style.css">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22js%2Fscript.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
page.html
<link rel="import" href="component.html" >
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22js%2Fhost-script.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
From the previous example, it can be concluded that we should include resources defined within the component.html file. That way, resources become available on the page where the import has been performed.
While performing an import, we should keep in mind the following:
- If a resource has been linked from several imports, it will be included just once. Because of that, if several components use jQuery, it will be included just once.
- Just like with Ajax calls, if you want to reach import from other domain, it has to be CORS enabled.
- Import can access it’s own DOM, but also connected DOM’s page .
- Likewise, you can access DOM of the document that has been imported.
- Script from the imported file behaves like script tag with defer attribute, so script is executed when the page has finished parsing by defined order.
Templates
Inclusion of templates has always been lacking in standards. Because of that, semantically incorrect tags have been used in that purpose, like <script> or <textarea> tag, in order to prevent execution of code within those tags. With new <template> element, you can save the content that isn’t going to be rendered, but will be available for use.
Example:
HTML
<table id="producttable">
<thead>
<tr>
<td>UPC_Code</td>
<td>Product_Name</td>
</tr>
</thead>
<tbody>
<!-- existing data could optionally be included here -->
</tbody>
</table>
Old approach
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Ftemplate%22%20id%3D%22productrow%22%3E%0A%20%20%3C%2Fp%3E%0A%3Ctr%3E%0A%20%20%20%20%3C%2Fp%3E%0A%3Ctd%20class%3D%22record%22%3E%3C%2Ftd%3E%0A%3Cp%3E%0A%20%20%20%20%3C%2Fp%3E%0A%3Ctd%3E%3C%2Ftd%3E%0A%3Cp%3E%0A%20%20%3C%2Ftr%3E%0A%3Cp%3E%0A%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
New approach
<template id="productrow">
<tr>
<td class="record"></td>
<td></td>
</tr>
</template>
Shadow DOM
Shadow DOM allows encapsulation of JavaScript, styles and templates in web components, so that they can stay separated from the main DOM of a page. That way, we have the ability to decide which parts of components will the end user be able to access.
We have to distinguish three main terms:
- Light DOM
- Shadow DOM
- Composed DOM
Example:
HTML
Light DOM
<div class="custom-element">
<span>Light DOM</span>
</div>
Shadow DOM is new structure inserted after initialization that is invisible to end user. In the example below you can see how to add structure into Shadow DOM element. The most important is createShadowRoot method by which we access and add new elements in Shadow DOM.
Shadow DOM
#shadow-root
<span>Shadow DOM - red</span>
<span class="green-span">Shadow DOM - green</span>
Encapsulation
var el = document.querySelector('.custom-element');
var shadow = el.createShadowRoot();
var inner = el.querySelector('span');
shadow.innerHTML += '<span>Shadow DOM - red</span>
';
shadow.innerHTML += '<span class="green-span">Shadow DOM - green</span>
';
shadow.innerHTML += '<content></content>';
shadow.innerHTML += '
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3Espan%20%7B%20color%3A%20red%3B%20%7D%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" />
';
The final structure is called Composed DOM. It isn’t visible to end user but it presents the result that is shown within browser.
Composed DOM
<div class="custom-element">
<span>Shadow DOM - red</span>
<span class="green-span">Shadow DOM - green</span>
<span>Light DOM</span>
</div>
It is possible to style all elements of defined component. In this case we can see that predefined color of Shadow elements is red. It gets overwritten by green color by using “::shadow” selector in order to access shadow elements of component. In the end we can see that we can directly access Light DOM elements since they were initially available in the document.
Style:
.custom-element span{
color:gray;
}
.custom-element::shadow .green-span{
color:green;
}
Output:
Shadow DOM - red
Shadow DOM - green
Light DOM
See the complete code via link: https://jsfiddle.net/bpmh34uo/1/
Create your own Web Component
Now that we have all the necessary parts, we can start creating our own Web Components. For this example, we will use the element that we’ve already mentioned before.
First, create file text-truncation.html.
Within that file, define the template that you will use within the component.
In this case, it will be the simple template that will contain an ellipsis if the text is too long.
HTML
<template>
<span>...</span>
</template>
Further on, we have to select the local document and the document of the page within which the component will be used. It is important to note that we will use _currentScript if we use a web component polyfill. Otherwise, currentScript will be used if the browser supports HTML Imports.
JS
var hostDoc = document;
var thisDoc = (hostDoc.currentScript || hostDoc._currentScript).ownerDocument;
After that, we take the content of the template that will be used to show an ellipsis in the case of the text being too long.
JS
var template = thisDoc.querySelector('template').content;
Next, we will initialize the element prototype and then save the initial text length and original text, but also cache the paragraph so that we can use it later.
JS
var TruncateTextEl = Object.create(HTMLElement.prototype);
TruncateTextEl.textLength = 100;
TruncateTextEl.paragraph;
TruncateTextEl.originalText;
Method createdCallback is a standard web component event and it is called when we create an element during initialization. Within it, we first save the paragraph element and the original text. Then, we access Shadow DOM and place the paragraph within it. Afterwards, we do the initial setting of the text length.
TruncateTextEl.createdCallback = function() {
this.paragraph = this.querySelector('p');
this.originalText = this.paragraph.textContent;
var shadowRoot = this.createShadowRoot();
shadowRoot.appendChild(this.paragraph);
if (this.hasAttribute('text-length')) {
var textLength = this.getAttribute('text-length');
this.setTextLength(textLength);
} else {
this.setTextLength(this.textLength);
}
};
Method attributeChangedCallback is also a standard event that is called when a change of the element’s attribute occurs. We have to check which attribute it is and, if it is text-length, we have to set the text length again and it’s shortening (if necessary).
TruncateTextEl.attributeChangedCallback = function(attr, oldVal, newVal) {
if (attr === 'text-length') {
this.setTextLength(newVal);
}
};
A Web Component created this way can be approached like any other HTML element, which is especially significant in terms of flexibility. That means that a component written in this way can be used everywhere, independently of the library and framework.
Example:
<em>You can change the text length dynamically:</em>
var el = document.querySelector('truncate-text');
el.setTextLength(120);
You can edit the attribute directly:
el.setAttribute('text-length', 150);
You can create an element dynamically:
var newEl = document.createElement('truncate-text');
newEl.setTextLength(120);
document.body.appendChild(newEl);
Integration is very simple and implies inclusion of trunicate-text.html in the page and definition of Light DOM - basic component structure. For now, it is also necessary to include the webcomponents.js polyfil too, so that this example can work in browsers that don’t support web components.
Example:
HTML
<html>
<head>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20data-require%3D%22webcomponentsjs%400.7.5%22%20data-semver%3D%220.7.5%22%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fwebcomponentsjs%2F0.7.5%2Fwebcomponents.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<link rel="import" href="truncate-text.html" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22script.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
</head>
<body>
<truncate-text text-length="150">
Quisque velit nisi, pretium ut lacinia in, elementum id enim.
Donec rutrum congue leo eget malesuada. Vestibulum ac diam
sit amet quam vehicula elementum sed sit amet dui. Curabitur
arcu erat, accumsan id imperdiet et, porttitor at sem. Curabitur
non nulla sit amet nisl tempus convallis quis ac lectus.
Quisque velit nisi, pretium ut lacinia in, elementum id enim.
Vivamus suscipit tortor eget felis porttitor volutpat. Curabitur
arcu erat, accumsan id imperdiet et, porttitor at sem. Sed
porttitor lectus nibh. Curabitur non nulla sit amet nisl tempus
convallis quis ac lectus.
</truncate-text>
</body>
Result:
Quisque velit nisi, pretium ut lacinia in, elementum id enim. Donec rutrum congue leo eget malesuada. Vestibulum ac diam sit amet qu …
You can find the complete example along with the integration here: https://plnkr.co/edit/UwD8u6ySiCnvZcjpUvTw?p=preview (link is external)
Conclusion
Web components will definitely cause some important changes in the development of web applications. Ease of use, flexible implementation, and high modularity represent the positive aspects of this technology. Web components will definitely play an important role in the future of web development.
Of course, there are also some less desirable effects of web component usage: the necessity of using webcomponents.js polyfill, for example, or issues with loading large number of components within a page, which result in a lot of https calls. Some of these problems can already be solved by, for example, using vulcanize tools (https://github.com/Polymer/vulcanize - the link is external) that integrates files into just one file, speeding up page loading.