⬆️ ⬇️

Introduction to web components. Part 1

From the translator: I present to you the translation of Google’s promising Web Component standard , which may become a trend in the next few years. At the moment, the knowledge of this standard does not bear practical application, therefore, if you are not a fan of everything new and interesting, you may be bored to read this translation.

The translation is posted on github , so if you want to help with the translation or correct the error, make a pull request, well, or write in a personal.



Status:



The authors:



')

Introduction





The component model for the Web (or Web Components) consists of four modules, which, when used together, allow web application developers to create widgets with rich visual capabilities that are easy to develop and reuse, which is currently not possible using only CSS and JS libraries.





Modules:







Together, decorators and custom elements are called components.



Templates





The <template> element contains markup intended for use later with a script or other module that can use a template (for example, <decorator> and <element> , which are described below).



The content of the <template> element is parsed by the parser, but it is inactive: scripts do not run, images are not loaded, and so on. <template> The element is not displayed.



In the script, such an element has a special content property, which contains a static DOM structure defined in the template.



For example, a developer may need to define a DOM structure that is created several times in a document, and then create an instance of it when necessary.



 <template id="commentTemplate"> <div> <img src=""> <div class="comment"></div></div> </template> var t = document.querySelector("#commentTemplate"); //     img[src]  . // … someElement.appendChild(t.content.cloneNode()); 




Adding a static DOM node to a document makes it “live”, as if this DOM node was obtained through the innerHTML property.



Decorators





A decorator is something that improves or redefines the representation of an existing element. Like all aspects of representations, the behavior of decorators is controlled by CSS. However, the ability to define additional aspects of a view using markup is a unique feature of decorators.



The <decorator> element contains a <template> element that defines the markup used to render the decorator.



 <decorator id="fade-to-white"> <template> <div style="position: relative;"> <style scoped> #fog { position: absolute; left: 0; bottom: 0; right: 0; height: 5em; background: linear-gradient( bottom, white 0, rgba(255, 255, 255, 0) 100); } </style> <content></content> <div id="fog"></div> </div> </template> </decorator> 




The <content> element points to the place where the decorator (more precisely, its contents) should be inserted.



The decorator is applied using the CSS decorator property:



 .poem { decorator: url(#fade-to-white); font-variant: small-caps; } 




The decorator and CSS described above will force this markup:



 <div class="poem"> Two roads diverged in a yellow wood,<br></div> 


+ But its render will be like with this markup (user agent styles are omitted for brevity):



 <div class="poem" style="font-variant: small-caps;"> <div style="position: relative;"> Two roads diverged in a yellow wood,<br><div style="position: absolute; left: 0; …"></div> </div> </div> 


If the document has changed so that the CSS selector where the decorator was declared is no longer valid — usually when the selector with the decorator property no longer applies to an element or the rule with the decorator has been changed in the style attribute, the decorator is no longer applied, returning the rendering of the element in original condition ..



Even though the decorator CSS property can point to any resource on the network, the decorator will not be applied while its definition is loaded into the current document.



The markup that is generated by views is limited to purely presentational applications: it can never run a script (including embedded event handlers), and it cannot be editable.



Events in the decorators





Decorators can also hang event handlers to implement interactivity. Since decorators are transient , it is not effective to hang event handlers on the nodes in the template or to rely on any state of the template, because the nodes in the template are reassembled each time the decorator is applied or removed from the element.



Instead, decorators register event handlers with the event controller. To register an event handler, the template includes a <script> element. The script is run once when the decorator is parsed or inserted into the document, or loaded as part of an external document.



Fig. Register event handlers



Register event handlers



The event controller will be passed to the script as the value of this .



 <decorator id="decorator-event-demo"> <script> function h(event) { alert(event.target); } this.listen({selector: "#b", type: "click", handler: h}); </script> <template> <content></content> <button id="b">Bar</button> </template> </decorator> 




Calling the lisnen function means that when the button is pressed, the event handler will trigger.



The event controller will forward the event that occurred in on any node on which the decorator was applied to the event handler.



Fig. Event Handling and Reassignment



Event Handling and Reassignment



When an event listener is invoked, the target value of the event is the node to which the decorator was applied, and not the contents of its template. For example, if the decorator specified above has the following content:



 <span style="decorator: url(#decorator-event-demo);">Foo</span> 


Rendered to:



  Foo [bar] 


Clicking the button will display a message with [object HTMLSpanElement] .



Redefinition of the target property is necessary, so the decorator defines the display; it does not affect the structure of the document. While the decorator is applied, the target property is overridden by the node on which it is applied.



Also, if the script changes the content of the template, the changes are ignored, just as setting the textContent of the <script> element does not entail executing the script again.



The decorator cannot change his pattern in any way and affect the display of himself on the element; he can only redefine the decorator to another.



Examples of decorators





An example of how a decorator can be used to create a simple variant of a detail element:



 details { decorator: url(#details-closed); } details[open] { decorator: url(#details-open); } 


 <decorator id="details-closed"> <script> this.listen({ selector: "#summary", type: "click", handler: function (event) { event.currentTarget.open = true; } }); </script> <template> <a id="summary"> > <content select="summary:first-of-type"></content> </a> </template> </decorator> <decorator id="details-open"> <script> this.listen({ selector: "#summary", type: "click", handler: function (event) { event.currentTarget.open = false; } }); </script> <template> <a id="summary"> V <content select="summary:first-of-type"></content> </a> <content></content> </template> </decorator> 




It took two decorators. One represents the detail element in a closed form, the other in the open. Each decorator uses a mouse click event handler to change the state of open / closed. The select attribute of the <element> will be discussed in more detail below.



Custom items





Custom elements - a new type of DOM elements that can be defined by the author.



Custom elements can define the appearance of the display through decorators. Unlike decorators, which can be applied or removed to the desired element, the type of the custom element is fixed. But custom elements can define a completely new display and behavior that cannot be defined through decorators, due to the elementary nature of the latter.



The <element> defines a custom element.



 <element extends="button" name="x-fancybutton"></element> 




The extends attribute defines an element whose functionality we want to expand. Each instance of the user element will have a tagName defined in the extends attribute.



The name attribute defines a custom element that will be associated with this markup. The namespace of the name attribute is the same as the tag names of the standard elements, so to eliminate collisions, use the prefix x-.



Different browsers define HTML elements in different ways, but all their interpretations are guided by HTML semantics.



Because not all browsers support custom elements, authors should extend the HTML elements that have the closest meaning to the new custom element. For example, if we define a custom item that is interactive and responds to clicks by performing some actions, we must expand the button ( <button> ).



When there is no HTML element that is semantically close to the right one, the author should extend a neutral element, such as <span> .



Representation





A custom element can contain a pattern:



 <element extends="button" name="x-fancybutton"> <template> <style scoped> ::bound-element { display: transparent; } div.fancy { … } </style> <div class="fancy"> <content></content> <div id="t"></div> <div id="l"></div> <div id="b"></div> <div id="r"></div> </div> </template> </element> 




If the custom element contains a template, a copy of that template will be inserted into the shadow DOM element by the custom element designer.



The shadow DOM will be described below.



Using custom elements in markup





Since custom elements I use existing HTML tags (div, button, option, etc.), we need an attribute to determine when we want to use a custom element. This attribute is is , and its value is the name of the user element. For example:



 <element extends="button" name="x-fancybutton"> <!-- definition --></element> <button is="x-fancybutton" onclick="showTimeClicked(event);"> <!-- use --> Show time </button> 




Using custom elements in scripts





You can create a custom element from a script using the standard document.createElement method:



 var b = document.createElement("x-fancybutton"); alert(b.outerHTML); // will display '<button is="x-fancybutton"></button>' 




Also, you can set the constructor attribute on the <element> to explicitly specify the name of the element constructor that will be exported to the window object. This constructor can be used to create a custom element:



 <element extends="button" name="x-fancybutton" constructor="FancyButton"></element> 


...

 var b = new FancyButton(); document.body.appendChild(b); </code></pre> 




A custom element can declare API methods by adding them to its prototype , in the <script> element, inside the <element> :



 <element extends="button" name="x-fancybutton" constructor="FancyButton"><script> FancyButton.prototype.razzle = function () { … }; FancyButton.prototype.dazzle = function () { … }; </script> </element><script> var b = new FancyButton(); b.textContent = "Show time"; document.body.appendChild(b); b.addEventListener("click", function (event) { event.target.dazzle(); }); b.razzle(); </script> 




In order to provide simple degradation, this inside the <script> element points to the parent element of type HTMLElementElement :



 <element extends="table" name="x-chart" constructor="Chart"> <script> if (this === window) // Use polyfills to emulate custom elements. // … else { // … } </script> </element> 




Technically, a script inside a <script> element, when it is nested in a <element> or <decorator> , is executed identically to such a call:



 (function() { // code goes here. }).call(parentInstance); 




In situations where the name of the constructor in the window object is unknown, the author of the custom component can use the generatedConstructor property of the HTMLElementElement :



 <element extends="table" name="x-chart"> <script> // … var Chart = this.generatedConstructor; Chart.prototype.shizzle = function() { /* … */ }; // … </script> </element> 




You cannot create a custom member specifying the is attribute of an existing DOM element. Running the following code will not do anything:



 var div = document.createElement("div"); div.setAttribute("is", "foo"); alert(div.is); // displays null alert(div.outerHTML); // displays <div></div> 




Item update





When the custom element declaration is loaded, each element with the is attribute set to the name of the custom element will be updated to the custom element. The update should be identical to deleting an item and replacing it with a user item.



When each element is replaced with a non-bubbling, a non-cancellable event occurs on the element being removed. A script that wants to delay interaction with the rest of the document until the user element loads into the special event:



 function showTimeClicked(event) { // event.target may be an HTMLButtonElement or a FancyButton if (!event.target.razzle) { // razzle, part of the FancyButton API, is missing // so upgrade has not happened yet event.target.addEventListener('upgrade', function (upgradeEvent) { showTime(upgradeEvent.replacement); }); return; } showTime(event.target); } function showTime(b) { // b is FancyButton } 




Authors who want to avoid displaying non-stylized content can use CSS to change the display of a non-replaced, regular element, until the custom element is loaded.



Life Cycle Methods





A user member can subscribe to four lifecycle methods:





Handlers are invoked with this pointing to the element.



The inserted and removed handlers can be invoked several times, each time a user element is inserted and removed.



You can subscribe to these handlers by calling the HTMLElementElement.lifecycle method:



 <element extends="time" name="x-clock"> <script> // … this.lifecycle({ inserted: function() { this.startUpdatingClock(); }, removed: function() { this.stopUpdatingClock(); } }); // … </script> </element> 




Extend custom items





In addition to HTML elements, you can extend a custom element by specifying the name of the custom element in the extends attribute of the <element> :



 <element extends="x-clock" name="x-grandfatherclock"></element> 




Error processing





There are several possibilities for handling errors when rendering custom elements:







From the translator: in the next part, we will look at the use of shadow DOM in Web components, about external user elements and decorators, and about the isolation, restriction and encapsulation of Web components.

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



All Articles