📜 ⬆️ ⬇️

Web Components - the future of the Web

After some time, it became clear that the main idea of ​​Prototype came into conflict with the world. The creators of the browsers responded to the resurgence of Javascript by adding new APIs, many of which clashed with the implementation of Prototype.

- Sam Stephenson, creator of Prototype.js, You Are Not Your Code

The creators of browsers do harmoniously. The decision about the new API is made taking into account current trends in the opensource communities. So prototype.js contributed to Array.prototype.forEach() , map() , etc., jquery inspired developers to HTMLElement.prototype.querySelector() and querySelectorAll() .

The client side code becomes more complex and more voluminous. Numerous frameworks appear that help keep this chaos under control. Backbone , ember , angular, and others created to help write clean, modular code. Application level frameworks are a trend. His spirit has been present in the JS environment for some time. Not surprisingly, the creators of browsers decided to pay attention to it.

Web Components is a draft set of standards. He was offered and actively promoted by the guys from Google, but the initiative was already supported by Mozilla. And Microsoft. Just kidding, Microsoft is not doing anything at all. Opinions in the community are controversial (judging by the comments, articles, etc.).
')
The basic idea is to allow programmers to create widgets. Application fragments that are isolated from the document into which they are embedded. It is possible to use the widget using both HTML and JS API.

I have been playing with new APIs for a couple of weeks and I am sure that in some form, sooner or later these features will be in browsers. Although their implementation in Chrome Canary sometimes puzzled me (me, and Chrome Canary itself), Web Components seems to be the tool I missed.

The Web Components standard consists of the following parts:


There are more parts and small parts in the Web Components. Some I will still mention, until some have not reached.

Templates


The concept of templates is simple. Although this word in the standard means not what we are used to.

In modern web frameworks, templates are strings or DOM fragments into which we substitute data before showing it to the user.

In web components, templates are DOM fragments. The browser parses their contents, but does not execute until we insert it into the document. That is, the browser will not load pictures, audio and video, will not execute scripts.

For example, such a fragment of markup in the document will not cause the image to load.

  <template id="tmpl-user"> <h2 class="name"> </h2> <img src="photo.jpg"> </template> 

Although the browser parses the contents of <template> . You can reach it using js:

  var tmpl = document.querySelector('#tmpl-user'); //  <template> var content = tmpl.content; var imported; //    : content.querySelector('.name').innerText = ''; //        , //  document.importNode() // //    ``  , //       `photo.jpg` imported = document.importNode(content); //     : document.body.appendChild(imported); 


An example of the work of templates can be found here .

All examples in the article should be viewed in Chrome Canary with flags enabled:

  • Experimental Web Platform features
  • Enable HTML Imports
  • Enable Experimental Javascript


For what?


At the moment there are three ways to work with templates:

  1. Add a template to a hidden item on the page. When you need it,
    copy and substitute data:

      <div hidden data-template="my-template"> <p>Template Content</p> <img></img> </div> 

    The disadvantages of this approach are that the browser tries to “execute” the template code. That is, upload pictures, execute script code, etc.

  2. Get the contents of the template as a string (request by AJAX or from <script type="x-template"> ).

      <sctipt type="x-template" data-template="my-template"> <p>Template Content</p> <img src="{{ image }}"></img> </script> 

    The downside is that you have to work with strings. This poses a XSS threat; additional attention should be paid to shielding.

  3. Compiled templates, like hogan.js , also work with strings. It means that they have the same flaw as the templates of the second type.


<template> does not have these flaws. We work with DOM, not with strings. When to execute the code, we also decide.

Shadow dom


Encapsulation. This is what I lacked most in working with markup. What is a Shadow DOM and how it works is easier to understand by example.

When we use the html5 element, the <audio> code looks like this:

  <audio controls src="kings-speech.wav"></audio> 


But on the page it looks like this:

audio element

We see a lot of controls, progress bar, audio length indicator. Where do these elements come from and how can we get to them? The answer is they are in the Shadow Tree element. We can even see them in DevTools, if we want.

In order for Chrome in DevTools to display the contents of the Shadow DOM, in the settings for DevTools, the General tab, in the Elements section, check Show Shadow DOM .

The contents of the Shadow DOM of the <audio> in DevTools:

devtools shadow dom

example link

Shadow DOM Theory

Shadow Tree is a subtree that is attached to an element in a document. The element in this case is called the shadow host , in its place the browser shows the contents of the shadow tree , ignoring the contents of the element itself.

This is what happens with the <audio> tag in the example above, in its place, the browser renders the contents of the shadow tree .

The trick of the shadow dom is that the styles defined in it using <style> do not apply to the parent document. We also have the ability to limit the influence of the styles of the parent document on the contents of the shadow tree. More on that later.

Plant a shadow tree

Shadow DOM API allows users to independently create and
manipulate the contents of shadow tree .

example
  <div class="shadow-host">     . </div> <script> var shadowHost = document.querySelector('.shadow-host'); var shadowRoot = shadowHost.createShadowRoot(); shadowRoot.innerText = '   .' </script> 

Result:

custom shadow dom

example link

Projections, <content>


The projection is the use of the contents of the host in the shadow tree . For this, there is a <content> tag in the standard.

It is important that <content> projects the contents of the host, rather than transferring it from the host to the shadow tree. The descendants of the host remain in place, the document styles (and not the shadow tree) apply to them. <content> is a kind of window between worlds.

example
  <template id="content-tag"> <p>   <strong>shadow tree</strong>. </p> <p>    <strong>shadow host</strong>: </p> <content></content> </template> <div class="shadow-host"> <h1 class="name"></h1> <img src="varlam.png"> <p class="description"> </p> </div> <script> var host = document.querySelector('.shadow-host'), template = document.querySelector('#content-tag'), shadow = host.createShadowRoot(); shadow.appendChild(template.content); </script> 

Result:

content demo

example link

Shadow DOM Styles


Style encapsulation is the main feature of the shadow DOM . The styles that are defined in the shadow tree are valid only inside this tree.

An annoying feature is that you cannot use external css files in shadow tree . Hope this is corrected in the future.

example
  <template id="color-green"> <style> div { background-color: green; } </style> <div></div> </template> <div class="shadow-host"></div> <script> var host = document.querySelector('.shadow-host'), template = document.querySelector('#color-green'), shadow = host.createShadowRoot(); shadow.appendChild(template.content); </script> 

The green background in the example will only get `<div>` inside the shadow tree. Thats
There are styles "not flow" in the main document.

Result:

green

example link

Inherited styles


By default, inherited styles, such as color , font-size and others , affect the contents of shadow tree. We avoid this by setting shadowRoot.resetStyleInheritance = true .

example
  <template id="reset"> <p>    .</p> <content></content> </template> <div class="shadow-host"> <p>Host Content</p> </div> <script> var host = document.querySelector('.shadow-host'), template = document.querySelector('#reset'), shadow = host.createShadowRoot(); shadow.resetStyleInheritance = true; shadow.appendChild(template.content); </script> 

Result:

inherit styles

example link

Copyright styles


To make document styles influence how a shadow tree looks, use the applyAuthorStyles property.

example
  <template id="no-author-st"> <div class="border">div.border</div> </template> <style> /*    */ .border { border: 3px dashed red; } </style> <div class="shadow-host"></div> <script> var host = document.querySelector('.shadow-host'), template = document.querySelector('#no-author-st'), shadow = host.createShadowRoot(); shadow.applyAuthorStyles = false; //  - shadow.appendChild(template.content); </script> 


Changing the value of applyAuthorStyles , we get a different result:

applyAuthorStyles = false

no to author styles

applyAuthorStyles = true

yes to author styles

example link, applyAuthorStyles = false

example link, applyAuthorStyles = true

Selectors ^ and ^^


Encapsulation is great, but if we still want to get to the shadow tree and change its presentation from the document styles, we will need a hammer. And a sledgehammer.

The div ^ p selector is similar to div p with the exception that it crosses one shadow border ( Shadow Boundary ).

The selector div ^^ p similar to the previous one, but crosses ANY number of shadow borders.

example
  <template id="hat"> <p class="shadow-p">   . </p> </template> <style> /*    */ .shadow-host ^ p.shadow-p { color: red; } </style> <div class="shadow-host"></div> <script> var host = document.querySelector('.shadow-host'), template = document.querySelector('#hat'), shadow = host.createShadowRoot(); shadow.appendChild(template.content); </script> 

Result:

cat in the hat

example link

Why do I need a Shadow DOM?


Shadow DOM allows you to change the internal representation of HTML elements, leaving the external representation unchanged.

A possible use is an alternative to the iframe . The latter is too isolated. In order to interact with an external document, you have to invent crazy ways to send messages. Changing the external view using css is simply not possible.

Unlike iframe , the Shadow DOM is part of your document. And although the shadow tree is somewhat isolated, if we wish, we can change its presentation with the help of styles, or pick it up with a script.

Custom elements


Custom Elements is a tool for creating your own HTML elements. The API of this part of the Web Components looks mature and resembles directives.
Angular . In combination with Shadow DOM and templates , custom elements allow you to create full-fledged widgets like <audio> , <video> or <input type="date"> .

To avoid conflicts, according to the standard, custom elements must contain a hyphen in their name. By default, they inherit HTMLElement . Thus, when the browser stumbles upon a <my-super-element> markup, it parses it as an HTMLElement . In the case of <mysuperelement> , the result will be an HTMLUnknownElement .

example
  <dog></dog> <x-dog></x-dog> <dl> <dt>dog type</dt> <dd id="dog-type"></dd> <dt>x-dog type</dt> <dd id="x-dog-type"></dd> </dl> <script> var dog = document.querySelector('dog'), dogType = document.querySelector('#dog-type'), xDog = document.querySelector('x-dog'), xDogType = document.querySelector('#x-dog-type'); dogType.innerText = Object.prototype.toString.apply(dog); xDogType.innerText = Object.prototype.toString.apply(xDog); </script> 

Result:

x-dog

example link

Custom element API


We can define properties and methods of our element. Such as the play() method of the <audio> element.

There are 4 events in the lifecycle of an element, for each we can hang a callback:


The algorithm for creating a custom element looks like this:

  1. Create a prototype element.

    The prototype must inherit from HTMLElement or its successor,
    for example, HTMLButtonElement :

      var myElementProto = Object.create(HTMLElement.prototype, { // API    lifecycle callbacks }); 

  2. Register the element in the DOM using document.registerElement() :

      var myElement = document.registerElement('my-element', { prototype: myElementProto }); 


example
  <x-cat></x-cat> <div> <strong>Cat's life:</strong> <pre id="cats-life"></pre> </div> <script> var life = document.querySelector('#cats-life'), xCatProto = Object.create(HTMLElement.prototype, { nickName: 'Cake', writable: true }); xCatProto.meow = function () { life.innerText += this.nickName + ': meow\n'; }; xCatProto.createdCallback = function () { life.innerText += 'created\n'; }; xCatProto.attachedCallback = function () { life.innerText += 'attached\n'; }; xCatProto.detachedCallback = function () { life.innerText += 'detached\n'; }; xCatProto.attributeChangedCallback = function (name, oldVal, newVal) { life.innerText += ( 'Attribute ' + name + ' changed from ' + oldVal + ' to ' + newVal + '\n'); }; document.registerElement('x-cat', { prototype: xCatProto }); document.querySelector('x-cat').setAttribute('friend', 'Fiona'); document.querySelector('x-cat').meow(); document.querySelector('x-cat').nickName = 'Caaaaake'; document.querySelector('x-cat').meow(); document.querySelector('x-cat').remove(); </script> 

Result:

cats life

example link

Why do we need Custom Elements?


Custom Elements is a step to semantic markup. It is important for programmers to create abstractions. Semantically neutral <div> or <ul> well suited for low-level layout, while Custom Elements will allow you to write modular, readable code at a high level.

Shadow DOM and Custom Elements provide the ability to create context-independent widgets, with a convenient API and an encapsulated internal view.

HTML Imports


Imports is a simple API that has long been a place in browsers. They provide the ability to insert into the document markup fragments from other files.

example
  <link rel="import" href="widget.html"> <sctipt> var link = document.querySelector('link[rel="import"]'); //         // *import*. var importedContent = link.import; importedContent.querySelector('article'); </sctipt> 


Object.observe ()


Another nice addition and part of the Web Components (it seems) is the API for tracking changes to the Object.observe() object.

This method is available in Chrome, if you enable the Experimental Web Platform features flag.

example
  var o = {}; Object.observe(o, function (changes) { changes.forEach(function (change) { // change.object     console.log('property:', change.name, 'type:', change.type); }); }); ox = 1 // property: x type: add ox = 2 // property: x type: update delete ox // property: x type: delete 

When the object is changed, the callback is called and an array is passed to it.
properties that have changed.

TODO widget


According to ancient tradition, armed with this knowledge, I decided to make a simple TODO widget. It uses parts of the Web Components, which I described in the article.

Adding a widget to a page is reduced to one import and one tag in the body of the document.

example
  <html> <head> <link rel="import" href="todo.html"> </head> <body> <x-todo></x-todo> </body> </html> <script> // JS API : var xTodo = document.querySelector('x-todo'); xTodo.items(); //   xTodo.addItem(taskText); //  xTodo.removeItem(taskIndex); //  </script> 


Result:

todo widget

demo link

Conclusion


With the development of html5, browsers have begun to natively support new media formats. Elements like the <canvas> also appeared. Now we have a huge number of opportunities to create interactive applications on the client. This standard also introduced the elements <article> , <header> , and others. The markup has become “make sense”, has acquired semantics.

In my opinion, Web Components is the next step. Developers will be able to create interactive widgets. They are easy to maintain, reuse, integrate.

Page code will not look like a set of “blocks”, “paragraphs” and “lists”. We can use items like “menu”, “news feed”, “chat”.

Of course, the standard is damp. For example, imports do not work as well as templates. Their use has ruined Chrome from time to time. But the amount of innovation is amazing. Even some of these features can make life easier for web developers. And some will significantly speed up the work of existing frameworks.

Some parts of the Web Components can be used now with the help of polyfiles. Polymer Project is a full-fledged application level framework that uses Web Components .

ƒ

Links




Eric Bidelman , a series of articles and videos about Web Components :

Source: https://habr.com/ru/post/210058/


All Articles