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:
- Templates ( templates ), define the part of inactive markup, which can be used in the future;
- decorators, using CSS to control visual and behavioral changes in templates;
- custom elements ( custom elements ), allow authors to define their own elements (including the presentation and API of these elements), which can be used in the main HTML document;
- hidden DOM ( shadow DOM ), defines how the presentation and behavior of decorators and custom elements are combined with each other in the DOM tree.
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
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
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"> … </element> <button is="x-fancybutton" onclick="showTimeClicked(event);"> 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);
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) </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() {
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> </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);
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) {
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:
- created - constructor call, creating an instance of ShadowRoot from the
<template>
element. In the absence of a <template>
, ShadowRoot is set to null . - attributeChanged — Called whenever an attribute of an element changes. Arguments: name, old value, new value.
- inserted - is called after the custom component is inserted into the DOM tree. In this handler, you can load resources for the user component or start timers.
- removed - called after the user element is removed from the DOM tree. Here you can stop the timers that are not needed when the user element is not in the DOM tree.
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> </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:
- An element's tagName does not match the value of the extends attribute of the custom element, for example:
<div is="x-fancybutton">
, but <element name="x-fancybutton" extends="button">
. In this case, the is attribute is discarded during parsing. - The value of the is attribute does not match any existing element. This situation is considered as if the custom element definition has not yet been loaded, and the element will be updated as soon as the definition is loaded.
- The value of the extends attribute does not match any existing element. In this case, the processing of the definition of the custom element will be held until the element indicated in the extends attribute is loaded.
- The value of the is attribute does not begin with x- . In this case, the is attribute is ignored.
- The value of the name attribute does not begin with x- . In this case, the definition of a user element is considered invalid and is ignored.
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.