- PVSM.RU - https://www.pvsm.ru -
От переводчика: Представляю вашему вниманию перевод многообещающего стандарта Веб-компонентов [1] от Google, который может стать трендом в ближайшие несколько лет. В данный момент, знание этого стандарта не несёт практического применения, поэтому, если вы не фанат всего нового и интересного, вам возможно будет скучно читать данный перевод.
Перевод выложен на github [2], поэтому, если вы хотите помочь с переводом или исправить ошибку сделайте pull request, ну или пишите в личку.
Статус: Эксперементальный драфт
Авторы:
Компонентная модель для Web'а (или Web Components) состоит из четырёх модулей, которые, будучи использованы вместе, позволят разработчикам web-приложений создавать виджеты с богатыми визуальными возможностями при этом легкие в разработке и переиспользовании, что на данный момент невозможно при использовании только CSS и JS-библиотек.
Эти модули:
Вместе декораторы и пользовательские элементы называются компонентами
Элемент <template>
содержит разметку предназначенную для использования позже с помощью скрипта или другого модуля, который может использовать шаблон (например, <decorator>
и <element>
, которые описаны ниже).
Содержимое элемента <template>
разбирается анализатор, но оно статично: скрипты не запускаются, картинки не грузятся, и т.д. <template>
элемент не рендерится.
В скрипте такой элемент имеет специальное свойство content, которое содержит статическую DOM-структуру определённую в шаблоне.
Например, разработчику может понадобиться определить DOM-структуру, которая создается несколько раз в документе, а затем создать его экземпляр когда это необходимо.
<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>
Добавление статического DOM-узла в документ делает его «живым», как будто этот DOM-узел был получен через свойство innerHTML.
Декоратор это нечто, что улучшает или переопределяет представление существующего элемента. Как и все аспекты представлений, поведение декораторов контролируется через CSS. Однако, возможность определять дополнительные аспекты представления используя разметку — уникальная черта декораторов.
Элемент <decorator>
содержит элемент <template>
, который определяет разметку используемую для рендеринга декоратора.
<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>
Элемент <content>
[9] указывает на место, куда декоратор (точнее, его содержимое) должно быть вставлено.
Декоратор применяется, используя css-свойство decorator:
.poem {
decorator: url(#fade-to-white);
font-variant: small-caps;
}
Декоратор и css описанные выше заставят данную разметку:
<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>
рендерится, как будто это была такая разметка (опуская браузерные стили для краткости):
<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>
Если документ изменился так, что css-селектор, где был объявлен декоратор, более не действителен, обычно когда селектор со свойством decorator более не применяется к элементу или правило с декоратором было изменено в атрибуте style элемента, декоратор более не применяется, возвращая рендеринг элемента в первоначальное состояние.
Даже несмотря на то, что css-свойство decorator может указывать на любой ресурс в сети, декоратор не будет применяться, пока его определение загружается в текущий документ.
Разметка, которая генерируется представлениями, ограничивается чисто презентационным применением: она никогда не может запустить скрипт (в том числе встроенные обработчики событий), и она не может быть доступна для редактирования.
Декораторы также могут навешивать обработчики событий для реализации интерактивности. Поскольку декораторы преходящи, не эффективно навешивать обработчики событий на ноды в шаблоне или полагаться на любое состояние шаблона, так как ноды в шаблоне пересобираются каждый раз как декоратор применяется или снимается с элемента.
Вместо этого, декораторы регистрируют обработчики событий у контроллера событий. Чтобы зарегистрировать обработчик событий, шаблон включает в себя элемент <script>
. Скрипт запускается один раз, когда декоратор парсится или вставляется в документ, или загружается как часть внешнего документа.
Рис. Регистрация обработчиков событий
Контроллер событий будет передан в скрипт в качестве значения 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>
Вызов функции lisnen означает, что когда кнопка будет нажата, сработает обработчик события.
Контроллер событий перенаправит событие, наступившее в на любой ноде, на которой декоратор был применён, в обработчик события.
Рис. Обработка и переназначение событий
Когда слушатель событий вызывается, значением target события является нода, на которую декоратор был применен, а не содержимое его шаблона. Например, если декоратор, указанный выше, такого содержания:
<span style="decorator: url(#decorator-event-demo);">Foo</span>
Рендерится в:
Foo[Bar]
Клик по кнопке покажет сообщение с [object HTMLSpanElement]
.
Переопределение свойства target необходимо, тк декоратор определяет отображение; он не влияет на структуру документа. Пока декоратор применён, свойство target переопределяется на ноду, на которую он применён.
Также, если скрипт меняет контент шаблона, изменения игнорируются, точно также, как установка textContent элемента <script>
не влечет за собой выполнение скрипта ещё раз.
Декоратор не может никак изменить свой шаблон и повлиять на отображение самого себя на элемент, он может только переопределить декоратор на другой.
Пример, как декоратор может быть применён для создания простого варианта элемента detail:
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>
Для этого понадобилось два декоратора. Один представляет detail элемент в закрытом виде, другой в открытом. Каждый декоратор использует обработчик события клика мыши, для изменения состояния открыт/закрыт. Атрибут select элемента <element>
будет рассмотрен подробнее ниже.
Пользовательские элементы — новый тип DOM-элементов, которые могут быть определены автором.
Пользовательские элементы могут определять вид отображения через декораторы. В отличии от декораторов, которые могут быть применены или сняты на нужный элемент, тип пользовательского элемента фиксирован. Но пользовательские элементы могут определять совершенно новое отображение и поведение, которые нельзя определить через декораторы, из-за элементарной природы последних.
Элемент <element>
определяет пользовательский элемент.
<element extends="button" name="x-fancybutton">
…
</element>
Атрибут extends определяется элемент, функционал которого мы хотим расширить. Каждый экземпляр пользовательского элемента будет иметь tagName определённый в атрибуте extends.
Атрибут name определяет пользовательский элемент, который будет связан с этой разметкой. Пространство имён у атрибута name такое же, как у имён тэгов стандартных элементов, поэтому для устранения коллизий, используется префикс x-.
Разные браузеры, определяют HTML элементы по-разному, однако все их интерпретации руководствуются семантикой HTML.
Тк не все браузеры поддерживают пользовательские элементы, авторы должны расширять HTML элементы, которые имеют наиболее близкое значение для нового пользовательского элемента. Например, если мы определяем пользовательский элемент, который является интерактивным и реагирует на клики, выполняя некоторые действия, мы должны расширять кнопку (<button>
).
Когда нету HTML элемента, семантически близкого к нужному, автор должен расширять нейтральный элемент, такой как <span>
.
Пользовательский элемент может содержать шаблон:
<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>
Если пользовательский элемент содержит шаблон, копия этого шаблона будет вставлена в теневой DOM элемента конструктором пользовательских элементов.
Теневой DOM будет описан ниже.
Т.к. пользовательские элементы использую существующие HTML тэги (div, button, option и тд), нам нужен атрибут для определения когда мы хотим использовать пользовательский элемент. Таким атрибутом является is, а значением его является название пользовательского элемента. Например:
<element extends="button" name="x-fancybutton"> <!-- definition -->
…
</element>
<button is="x-fancybutton" onclick="showTimeClicked(event);"> <!-- use -->
Show time
</button>
Вы можете создать пользовательский элемент из скрипта, используя стандартный метод document.createElement:
var b = document.createElement("x-fancybutton");
alert(b.outerHTML); // will display '<button is="x-fancybutton"></button>'
Также, вы можете установить атрибут constructor у элемента <element>
, чтобы явно указать название конструктора элемента, которое будет экспортировано в объект window. Этот конструктор может быть использован для создания пользовательского элемента:
<element extends="button" name="x-fancybutton" constructor="FancyButton">
…
</element>
…
var b = new FancyButton();
document.body.appendChild(b);
</code></pre>
Пользовательский элемент может объявлять методы API добавляя их в свой prototype, в элементе <script>
, находящимся внутри элемента <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>
Для того, чтобы обеспечить простую деградацию, this внутри элемента <script>
указывает на родительский элемент типа HTMLElementElement:
<element extends="table" name="x-chart" constructor="Chart">
<script>
if (this === window)
// Use polyfills to emulate custom elements.
// …
else {
// …
}
</script>
</element>
Технически, скрипт внутри элемента <script>
, когда он является вложенным в <element>
или <decorator>
, выполняется идентично такому вызову:
(function() {
// code goes here.
}).call(parentInstance);
В ситуациях, когда название конструктора в объекте window неизвестно, автор пользовательского компонента может воспользоваться свойством generatedConstructor у HTMLElementElement:
<element extends="table" name="x-chart">
<script>
// …
var Chart = this.generatedConstructor;
Chart.prototype.shizzle = function() { /* … */ };
// …
</script>
</element>
Нельзя создать пользовательский элемент указывая is атрибут у существующего DOM-элемента. Выполнение следующего кода ничего не даст:
var div = document.createElement("div");
div.setAttribute("is", "foo");
alert(div.is); // displays null
alert(div.outerHTML); // displays <div></div>
Когда объявление пользовательского элемента будет загружено, каждый элемент с атрибутом is установленном в имя пользовательского элемента будет обновлён до пользовательского элемента. Обновление должно быть идентично удалению элемента и замене его на пользовательский элемент.
Когда каждый элемент заменяется, не всплывающее (non-bubbling), неотменяемое (non-cancellable) событие возникает на удаляющемся элементе. Скрипт, который хочет задержать взаимодействие с остальной часть документа до тех пор, пока пользовательский элемент не загрузиться может подписаться на специальное событие:
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
}
Авторы, которые хотят избежать показа нестилизованного контента, могут использовать CSS для изменения отображения не заменённого, обычного элемента, пока пользовательский элемент не загрузиться.
Пользовательский элемент может подписаться на четыре метода жизненного цикла:
<template>
. При отсутствии <template>
, ShadowRoot устанавливается в null.Обработчики вызываются с this указывающим на элемент.
Обработчики inserted и removed могут быть вызваны несколько раз, каждый раз, когда пользовательский элемент вставляется и удаляется.
Подписаться на эти обработчики можно вызвав метод HTMLElementElement.lifecycle:
<element extends="time" name="x-clock">
<script>
// …
this.lifecycle({
inserted: function() { this.startUpdatingClock(); },
removed: function() { this.stopUpdatingClock(); }
});
// …
</script>
</element>
В дополнении к HTML-элементам, можно расширить пользовательский элемент указанием имени пользовательского элемента в атрибуте extends элемента <element>
:
<element extends="x-clock" name="x-grandfatherclock">
…
</element>
Есть несколько возможностей для обработки ошибок при рендеринге пользовательских элементов:
<div is="x-fancybutton">
, но <element name="x-fancybutton" extends="button">
. В этом случае, атрибут is отбрасывается во время парсинга.От переводчика: в следующей части мы рассмотрим применение shadow DOM в Веб-компонентах, про внешние пользовательские элементы и декораторы, и про изоляцию, ограничение и инкапсуляцию Веб-компонентов
Автор: termi
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/15679
Ссылки в тексте:
[1] стандарта Веб-компонентов: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html
[2] github: https://github.com/termi/CreativeWork/blob/WCE/RU_ru/Web%20Components%20Explained/Translation.md
[3] dominicc@google.com: mailto:dominicc@google.com
[4] dglazkov@chromium.org: mailto:dglazkov@chromium.org
[5] templates: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html#template-section
[6] decorators: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html#decorator-section
[7] custom elements: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html#custom-element-section
[8] shadow DOM: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html#shadow-dom-section
[9] <content>
: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#content-element
[10] Image: https://a248.e.akamai.net/camo.github.com/ed05e5eb90e88efa5ff3f0249abcd53635c9d43b/687474703a2f2f7777772e686162726173746f726167652e636f6d2f696d616765732f6576656e74686676662e706e67
[11] Image: https://a248.e.akamai.net/camo.github.com/d1e4b2fce97482e7d6ff7ff1f7c31c39039dcbe1/687474703a2f2f7777772e686162726173746f726167652e636f6d2f696d616765732f6576656e74727063702e706e67
Нажмите здесь для печати.