- PVSM.RU - https://www.pvsm.ru -
Спустя какое время стало ясно, что основная идея Prototype вошла в противоречие с миром. Создатели браузеров ответили на возрождение Javascript добавлением новых API, многие из которых конфликтовали с реализацией Prototype.
— Sam Stephenson, создатель Prototype.js, You Are Not Your Code [1]
Создатели браузеров поступают гармонично. Решение о новых API принимают с учётом текущих трендов в opensource сообществах. Так prototype.js способствовал появлению Array.prototype.forEach()
, map()
и т.д., jquery вдохновил разработчиков на HTMLElement.prototype.querySelector()
и querySelectorAll()
.
Код на стороне клиента становится сложнее и объёмнее. Появляются многочисленные фреймворки, которые помогают держать этот хаус под контролем. Backbone, ember, angular и другие создали, чтобы помочь писать чистый, модульный код. Фреймворки уровня приложения — это тренд. Его дух присутствует в JS среде уже какое-то время. Не удивительно, что создатели браузеров решили обратить на него внимание.
Web Components — это черновик набора стандартов. Его предложили и активно продвигают ребята из Google, но инициативу уже поддержали в Mozilla. И Microsoft. Шучу, Microsoft вообще не при делах. Мнения в комьюнити противоречивые (судя по комментариям, статьям и т.д.).
Основная идея в том, чтобы позволить программистам создавать “виджеты”. Фрагменты приложения, которые изолированы от документа, в который они встраиваются. Использовать виджет возможно как с помощью HTML, так и с помощью JS API.
Я пару недель игрался с новыми API и уверен, что в каком-то виде, рано или поздно эти возможности будут в браузерах. Хотя их реализация в Chrome Canary иногда ставила меня в тупик (меня, и сам Chrome Canary), Web Components кажется тем инструментом, которого мне не хватало.
Стандарт Web Components состоит из следующих частей:
Фрагменты HTML, которые программист собирается использовать в будущем.
Содержимое тегов <template>
парсится браузером, но не вызывает выполнение скриптов и загрузку дополнительных ресурсов (изображений, аудио…) пока мы не вставим его в документ.
Инструмент инкапсуляции HTML.
Shadow DOM позволяет изменять внутреннее представление HTML элементов, оставляя внешнее представление неизменным. Отличный пример — элементы <audio>
и <video>
. В коде мы размещаем один тег, а браузер отображает несколько элементов (слайдеры, кнопки, окно проигрывателя). В Chrome эти и некоторые другие элементы используют
Shadow DOM.
Custom Elements позволяют создавать и определять API собственных HTML элементов. Когда-нибудь мечтали о том, чтобы в HTML был тег <menu>
или <user-info>
?
Импорт фрагментов разметки из других файлов.
В Web Components больше частей и маленьких деталей. Некоторые я ещё буду
упоминать, до каких-то пока не добрался.
Концепция шаблонов проста. Хотя под этим словом в стандарте подразумевается не то, к чему мы привыкли.
В современных web-фреймворках шаблоны — это строки или фрагменты DOM, в которые мы подставляем данные перед тем как показать пользователю.
В web components шаблоны — это фрагменты DOM. Браузер парсит их содержимое, но не выполняет до тех пор, пока мы не вставим его в документ. То есть браузер не будет загружать картинки, аудио и видео, не будет выполнять скрипты.
К примеру, такой фрагмент разметки в документе не вызовет загрузку изображения.
<template id="tmpl-user">
<h2 class="name">Иван Иваныч</h2>
<img src="photo.jpg">
</template>
Хотя браузер распарсит содержимое <template>
. Добраться до него можно с помощью 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);
Пример работы шаблонов можно посмотреть здесь [6].
Все примеры в статье следует смотреть в Chrome Canary [7] со включенными флагами:
- Experimental Web Platform features
- Enable HTML Imports
На данный момент существует три способа работы с шаблонами:
<div hidden data-template="my-template">
<p>Template Content</p>
<img></img>
</div>
Минусы такого подхода в том, что браузер попытается “выполнить” код шаблона. То есть загрузить картинки, выполнить код скриптов и т.д.
<script type="x-template">
).
<sctipt type="x-template" data-template="my-template">
<p>Template Content</p>
<img src="{{ image }}"></img>
</script>
Минус в том, что приходится работать со строками. Это создаёт угрозу XSS, нужно уделять дополнительное внимание экранированию.
У <template>
нет этих недостатков. Мы работаем с DOM, не со строками. Когда выполнять код, также решать нам.
Инкапсуляция. Этого в работе с разметкой мне не хватало больше всего. Что такое Shadow DOM и как он работает проще понять на примере.
Когда мы используем html5 элемент <audio>
код выглядит примерно так:
<audio controls src="kings-speech.wav"></audio>
Но на странице это выглядит так:
Мы видим множество контролов, прогресбар, индикатор длины аудио. Откуда эти элементы и как до них добраться? Ответ — они находятся в Shadow Tree элемента. Мы можем даже увидеть их в DevTools, если захотим.
Чтобы Chrome в DevTools отображал содержимое Shadow DOM, в настройках DevTools, вкладка General, раздел Elements ставим галочку Show Shadow DOM.
Содержимое Shadow DOM тега <audio>
в DevTools:
ссылка на пример [9]
Shadow Tree — это поддерево, которое прикреплено к элементу в документе. Элемент в этом случае называется shadow host, на его месте браузер показывает содержимое shadow tree, игнорируя содержимое самого элемента.
Именно это происходит с <audio>
тегом в примере выше, на его месте браузер рендерит содержимое shadow tree.
Фишка shadow dom в том, что стили, определённые в нём с помощью <style>
, не распространяются на родительский документ. Также у нас есть возможность ограничить влияние стилей родительского документа на содержимое shadow tree. Об этом позже.
Shadow DOM API позволяет пользователям самостоятельно создавать и
манипулировать содержимым shadow tree.
<div class="shadow-host">
Этот текст пользователь не увидит.
</div>
<script>
var shadowHost = document.querySelector('.shadow-host');
var shadowRoot = shadowHost.createShadowRoot();
shadowRoot.innerText = 'Он увидит этот текст.'
</script>
Результат:
ссылка на пример [10]
<content>
Проекция — это использование содержимого хоста в shadow tree. Для этого в стандарте есть тег <content>
.
Важно, что
<content>
проецирует содержимое хоста, а не переносит его из хоста в shadow tree. Потомки хоста остаются на своём месте, на них распространяются стили документа (а не shadow tree).<content>
это своего рода окно между мирами.
<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>
Результат:
ссылка на пример [11]
Инкапсуляция стилей — основная фишка shadow DOM. Стили, которые определёны в shadow tree имеют силу только внутри этого дерева.
Досадная особенность — использовать в shadow tree внешние css файлы нельзя. Надеюсь, это поправят в будущем.
<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>
Зелёный фон в примере получит только `<div>` внутри shadow tree. То
есть стили «не вытекут» в основной документ.
Результат:
ссылка на пример [12]
По-умолчанию наследуемые стили, такие как color
, font-size
и другие [13], влияют на содержимое shadow tree. Мы избежим этого, если установим shadowRoot.resetStyleInheritance = true
.
<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>
Результат:
ссылка на пример [14]
Чтобы стили документа влияли на то, как выглядит shadow tree, используйте свойство applyAuthorStyles
.
<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>
Изменяя значение applyAuthorStyles
, получаем разный результат:
applyAuthorStyles = false
applyAuthorStyles = true
ссылка на пример, applyAuthorStyles=false [14]
ссылка на пример, applyAuthorStyles=true [14]
Инкапсуляция это здорово, но если мы всё таки хотим добраться до shadow tree и изменить его представление из стилей документа, нам понадобится молоток. И кувалда.
Селектор div ^ p
аналогичен div p
с тем исключением, что он пересекает одну теневую границу (Shadow Boundary).
Селектор div ^^ p
аналогичен предыдущему, но пересекает ЛЮБОЕ количество теневых границ.
<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>
Результат:
ссылка на пример [14]
Shadow DOM позволяет изменять внутреннее представление HTML элементов, оставляя внешнее представление неизменным.
Возможное применение — альтернатива iframe
. Последний чересчур изолирован. Чтобы взаимодействовать с внешним документом, приходится изобретать безумные способы передачи сообщений. Изменение внешнего представления с помощью css просто невозможно.
В отличие от iframe
, Shadow DOM — это часть вашего документа. И хотя shadow tree в некоторой степени изолировано, при желании мы можем изменить его представление с помощью стилей, или расковырять скриптом.
Custom Elements — это инструмент создания своих HTML элементов. API этой части Web Components выглядит зрело и напоминает директивы
Angular. В сочетании с Shadow DOM и шаблонами, кастомные элементы дают возможность создавать полноценные виджеты вроде <audio>
, <video>
или <input type="date">
.
Чтобы избежать конфликтов, согласно стандарту, кастомные элементы должны содержать дефис в своём названии. По-умолчанию они наследуют HTMLElement
. Таким образом, когда браузер натыкается на разметку вида <my-super-element>
, он парсит его как HTMLElement
. В случае <mysuperelement>
, результат будет HTMLUnknownElement
.
<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>
Результат:
ссылка на пример [15]
Мы можем определять свойства и методы у нашего элемента. Такие, как метод play()
у элемента <audio>
.
В жизненный цикл (lifecycle) элемента входит 4 события, на каждое мы можем повесить callback:
Алгоритм создания кастомного элемента выглядит так:
Прототип должен наследовать HTMLElement
или его наследника,
например HTMLButtonElement
:
var myElementProto = Object.create(HTMLElement.prototype, {
// API элемента и его lifecycle callbacks
});
document.registerElement()
:
var myElement = document.registerElement('my-element', {
prototype: myElementProto
});
<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 + ': meown';
};
xCatProto.createdCallback = function () {
life.innerText += 'createdn';
};
xCatProto.attachedCallback = function () {
life.innerText += 'attachedn';
};
xCatProto.detachedCallback = function () {
life.innerText += 'detachedn';
};
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>
Результат:
ссылка на пример [16]
Custom Elements это шаг к семантической разметке. Программистам важно создавать абстракции. Семантически-нейтральные <div>
или <ul>
хорошо подходят для низкоуровневой вёрстки, тогда как Custom Elements позволят писать модульный, удобочитаемый код на высоком уровне.
Shadow DOM и Custom Elements дают возможность создавать независимые от контекста виджеты, с удобным API и инкапсулированным внутренним представлением.
Импорты — простое API, которому давно место в браузерах. Они дают возможность вставлять в документ фрагменты разметки из других файлов.
<link rel="import" href="widget.html">
<sctipt>
var link = document.querySelector('link[rel="import"]');
// Доступ к импортированному документу происходит с помощью свойства
// *import*.
var importedContent = link.import;
importedContent.querySelector('article');
</sctipt>
Ещё одно приятное дополнение и часть Web Components (кажется), это API для отслеживания изменений объекта Object.observe()
.
Этот метод доступен в Chrome, если включить флаг Experimental Web Platform features.
var o = {};
Object.observe(o, function (changes) {
changes.forEach(function (change) {
// change.object содержит изменённую версию объекта
console.log('property:', change.name, 'type:', change.type);
});
});
o.x = 1 // property: x type: add
o.x = 2 // property: x type: update
delete o.x // property: x type: delete
При изменении объекта o
вызывается callback, в него передаётся массив
свойств, которые изменились.
Согласно древней традиции, вооружившись этими знаниями, я решил
сделать простой TODO-виджет. В нём используются части Web Components, о которых я рассказывал в статье.
Добавление виджета на страницу сводится к одному импорту и одному тегу в теле документа.
<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>
Результат:
ссылка на демо [17]
С развитием html5 браузеры стали нативно поддерживать новые медиа-форматы. Также появились элементы вроде <canvas>
. Теперь у нас огромное количество возможностей для создания интерактивных приложений на клиенте. Этот стандарт также представил элементы <article>
, <header>
, и другие. Разметка стала “иметь смысл”, приобрела семантику.
На мой взгляд, Web Components — это следующий шаг. Разработчики смогут создавать интерактивные виджеты. Их легко поддерживать, переиспользовать, интегрировать.
Код страницы не будет выглядеть как набор “блоков”, “параграфов” и “списков”. Мы сможем использовать элементы вроде “меню”, “новостная лента”, “чат”.
Конечно, стандарт сыроват. К примеру, импорты работают не так хорошо, как шаблоны. Их использование рушило Chrome время от времени. Но объём нововведений поражает. Даже часть этих возможностей способна облегчить жизнь web-разработчикам. А некоторые заметно ускорят работу существующих фреймворков.
Некоторые части Web Components можно использовать уже сейчас с помощью полифилов. Polymer Project [18] — это полноценный фреймворк уровня приложения, который использует Web Components.
ƒ
Eric Bidelman [22], серия статей и видео о Web Components:
Автор: filipovskii_off
Источник [29]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/53375
Ссылки в тексте:
[1] You Are Not Your Code: http://sstephenson.us/posts/you-are-not-your-code
[2] Templates: http://www.w3.org/TR/components-intro/#template-section
[3] Shadow DOM: http://w3c.github.io/webcomponents/spec/shadow/
[4] Custom Elements: http://www.w3.org/TR/components-intro/#custom-element-section
[5] Imports: http://www.w3.org/TR/components-intro/#imports-section
[6] здесь: http://filipovskii.github.io/web-components-demo/wc-templates/
[7] Chrome Canary: https://www.google.com/intl/en/chrome/browser/canary.html
[8] hogan.js: http://twitter.github.io/hogan.js/
[9] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-shadowdom-audio/
[10] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-shadowdom-custom/#1
[11] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-shadowdom-custom/#2
[12] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-shadowdom-styles/#1
[13] другие: http://www.impressivewebs.com/inherit-value-css/
[14] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-shadowdom-styles/#3
[15] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-custom-elements/#1
[16] ссылка на пример: http://filipovskii.github.io/web-components-demo/wc-custom-elements/#2
[17] ссылка на демо: http://filipovskii.github.io/web-components-demo/wc-todo-demo/
[18] Polymer Project: http://www.polymer-project.org/
[19] Web Components Intro: http://www.w3.org/TR/components-intro/
[20] Примеры к этой статье: http://filipovskii.github.io/web-components-demo/
[21] Bug 811542 — Implement Web Components: https://bugzilla.mozilla.org/show_bug.cgi?id=811542
[22] Eric Bidelman: http://www.html5rocks.com/en/profiles/#ericbidelman
[23] Shadow DOM 101: http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/
[24] Shadow DOM 201: CSS and Styling: http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201
[25] Shadow DOM 301: Advanced Concepts & DOM APIs: http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/
[26] Custom Elements: defining new elements in HTML: http://www.html5rocks.com/en/tutorials/webcomponents/customelements
[27] HTML Imports: #include for the web: http://www.html5rocks.com/en/tutorials/webcomponents/imports/
[28] <web>components</web> (видео): http://www.youtube.com/watch?v=eJZx9c6YL8k
[29] Источник: http://habrahabr.ru/post/210058/
Нажмите здесь для печати.