ТРЕНДЫ ФРОНТЭНДА
WEB COMPONENTS
http://goo.gl/BlIWaB
WEB COMPONENTS
Цели разработки:
- предоставить нативные средства разработки сложных UI-компонент с возможностью их расширения и повторного использования
- разработать средства описания шаблона компонента, его поведения, стилей отображения и методов их инкапсуляции в единую независимую сущность
Web Components является логичным развитием возможностей браузеров под влиянием UI-фреймворков - Prototype JavaScript Framework, The Yahoo! User Interface Library, Ext JS, JQuery, jQuery UI, Twitter Bootstrap.
WEB COMPONENTS
Состав:
Работа над Web Components ведется W3C WEBAPPS WG с 2008 г.
Первая имплементация вошла в Google Chrome v.25 (февраль 2013).
SHADOW DOM
Назначение
- объявление и использование независимых поддеревьев в основном DOM-дереве документа
- инкапсуляция структуры компонента (HTML), стилей его отображения (CSS) и описания его поведения (JavaScript)
Shadow DOM связывает в единое целое возможности технологий стека Web Components: HTML Imports, Custom Elements, HTML Templates
Внимание!
Для просмотра примеров используйте Chrome Canary с включенными флагами:
- Experimental Web Platform features (Включить экспериментальные функции веб-платформы)
chrome://flags/#enable-experimental-web-platform-features
- Enable HTML Imports (Разрешить импорт HTML-файлов)
chrome://flags/#enable-html-imports
Для отображения Shadow DOM-элементов в отладчике необходимо настроить Chrome Developer Tools:
- откройте Chrome Developer Tools - F12
- откройте панель настроек -
- на вкладке «General» включите «Show Shadow DOM».
SHADOW DOM
в нативных компонентах
Input File: <input type="file">
Input Text: <input type="text">
Input Button: <input type="button" value="Button">
- Input File:
- Input Text:
- Input Button:
SHADOW DOM
host, shadow root
- для элемента основного DOM-дерева (host-элемента) может быть создано «персональное» Shadow DOM-дерево ShadowRoot (одно или несколько)
- host-элемент отображает контент самого «свежего» ShadowRoot
SHADOW DOM
пример
<button class="bws"><u>Get time...</u></button>
<script>(function(){
var host = document.querySelector('.bws'); // находим host-элемент
host.onclick = function(){
var root = host.createShadowRoot(); // создаем ShadowRoot-деревo
root.textContent = (new Date()).toLocaleTimeString(); // наполняем
};
})();</script>
SHADOW DOM
управление контентом, механизм распределения
В теневом дереве отмечаются точками вставки (insertion points) позиции отображения контента из другого дерева, а механизм распределения отображает в них указанный контент, не меняя при этом ни одно из этих деревьев.
- content insertion points распределяет контент host-ноды, (<content/>, HTMLContentElement)
- shadow insertion point распределяет контент предыдущего ShadowRoot, (<shadow/>, HTMLShadowElement)
Distributed nodes - ноды, распределенные в Shadow DOM точками вставки, недоступны с помощью обычных селекторов.
SHADOW DOM
content insertion points, shadow insertion points
<button class="bws">Get time...</button>
<script>(function(){
var host = document.querySelector('.bws');
host.onclick = function(){
var root = host.createShadowRoot();
root.innerHTML = '<content></content><shadow></shadow> / '+
'<b>'+(new Date()).toLocaleTimeString()+'</b>';
};
})();</script>
HTML TEMPLATES
HTMLTemplateElement
/ <template />
- могут быть объявлены внутри
<head>
,<body>
,<frameset>
- контент шаблона анализируется парсером как HTML
- контент не активен
- контент не является частью документа
- контент хранится в поле
.content
объекта HTMLTemplateElement в виде объекта DocumentFragment
HTML TEMPLATES
пример
<button class="bws">Get time...</button>
<template class="bws">
<content></content><shadow></shadow> / <b></b>
</template>
<script>(function(){
var host = document.querySelector('button.bws'),
template = document.querySelector('template.bws');
host.onclick = function(){
var root = host.createShadowRoot(),
content = template.content.cloneNode(true);
content.querySelector('b').textContent = (new Date()).toLocaleTimeString();
root.appendChild(content);
};
})();</script>
SHADOW DOM
инкапсуляция стилей, shadow boundary
Каждое теневое дерево отделено от внешнего мира границей shadow boundary.
Применение стилей внутри shadow boundary (для host-элемента, для distributed-элементов) имеет свои особенности.
- граница непрозрачна изнутри
- пропускает наследуемые свойства стилей основного дерева
root.resetStyleInheritance // (default:false)
- может быть прозрачна для стилей основного документа
root.applyAuthorStyles // (default:false)
- селектор
:host
для host-элемента теневого дерева - селектор
::distributed
::content
для distributed-элементов, распределенных insertion points - селектор
^
указывает переход через одну shadow boundary - селектор
^^
указывает переход через любое число shadow boundary
SHADOW DOM
инкапсуляция стилей. пример
<button class="bws">Get time...</button>
<template class="bws">
<style>
:host { background-color:#0f0; }
:host:hover { background-color:#ff0; }
:host:active { background-color:#f00; }
b { color:#00f }
</style>
<content></content><shadow></shadow> / <b></b>
</template>
<script>(function(){
var host = document.querySelector('button.bws'),
template = document.querySelector('template.bws');
host.onclick = function(){
var root = host.createShadowRoot(),
content = template.content.cloneNode(true);
content.querySelector('b').textContent = (new Date()).toLocaleTimeString();
root.appendChild(content);
};
})();</script>
/
HTML IMPORTS
Инструменты импорта:
CSS: <link rel="stylesheet">
JS: <script src="">
VIDEO: <video>
AUDIO: <audio>
####HTML: ???!!!
Импорт разметки: <iframe>
AJAX
<script type="">
W3C HTML Imports : <link rel="import">
Особенности:
- повторные импорты отсекаются браузером
- валидируется браузером как HTML
- контент может использовать внешние ресурсы (js, css, img,…)
- для кросс-доменных импортов используется CORS
- onload / onerror events
- контент ресурса хранится в поле
.import
объекта HTMLLinkElement (<link />
) - контент представлен в виде объекта HTMLDocument
HTML IMPORTS
применение
- декомпозиция веб-приложения
- объединение загрузки ресурсов ( js, css, images, … )
index.html
<html>
<head>
<link rel="import" href="bootstrap.html">
</head>
<body>
<!-- ... -->
</body>
</html>
bootstrap.html
<link rel="stylesheet" href="bootstrap.css">
<link rel="stylesheet" href="fonts.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
HTML IMPORTS
динамическая загрузка. обработка событий
<button class="known">Load resource ...</button>
<button class="unknown">Load unknown resource ...</button>
<script>(function(){
function loader(href){
var link = document.createElement('link');
link.rel = 'import';
link.href = href;
link.onload = function(e) {
alert( link.href+'\n'+e.type+'\n'+
link.import.documentElement.innerHTML+'\n');
};
link.onerror = function(e) { alert(link.href+'\n'+e.type); };
document.head.appendChild(link);
}
document.querySelector('button.known')
.onclick=function(){loader('/assets/posts/2014-02-20/import.html')};
document.querySelector('button.unknown')
.onclick=function(){loader('/anyfile')};
})();</script>
HTML IMPORTS
динамическая загрузка шаблона
<button class="bws">Load my template ...</button>
<script>
(function(){
function loader(href){
var link = document.createElement('link');
link.rel = 'import';
link.href = href;
link.onload = function(e) {
alert(link.href+'\n'+e.type+'\n'+link.import.documentElement.innerHTML);
var template = link.import.querySelector('template'),
root = host.createShadowRoot();
root.appendChild(template.content.cloneNode(true));
};
link.onerror = function(e) { alert(link.href+'\n'+e.type); };
document.head.appendChild(link);
}
var host = document.querySelector('.bws');
host.onclick = function(){ loader('/assets/posts/2014-02-20/import.html') };
})();</script>
CUSTOM ELEMENTS
Эволюция верстки:
плоская
фреймы
табличная
слои
JS UI frameworks
семантическая разметка
bootsrap-like frameworks
Модальное окно bootstrap:
<div class="modal fade" id="myModal"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body"> ... </div>
<div class="modal-footer"> ... </div>
</div>
</div>
</div>
Завтра: ???
CUSTOM ELEMENTS
Завтра: Web component based UI framework
Модальное окно x-bootstrap-modal:
<x-bootstrap-modal hidden overlay cancel-click-hide esc-hide>
<x-bootstrap-modal-header>
Modal title<x-bootstrap-button type="close" />
<x-bootstrap-modal-header>
<x-bootstrap-modal-body> ... </x-bootstrap-modal-body>
<x-bootstrap-modal-footer> ... </x-bootstrap-modal-footer>
</x-bootstrap-modal>
Преимущества:
- следующий шаг к семантической разметке
- еще один уровень декомпозиции UI-компонентов:
разметка/стили/поведение структура/шаблон/стили/поведение - модульный, удобочитаемый код
- возможность создавать независимые от контекста виджеты с удобным API и инкапсулированным внутренним представлением
CUSTOM ELEMENTS
возможности
- создавать новые типы HTML/DOM элементов
- создавать новые типы расширением существующих
- логически объединять разнородный функционал в одном теге
- расширять API существующих DOM-компонентов
CUSTOM ELEMENTS
создание новых типов
####document.registerElement('tag-name', [{prototype:{}}])
- имя тега должно содержать дефис
- прототип создаваемого элемента (
HTMLElement.prototype
)
var XElement = document.registerElement('x-element'),
xe = new XElement();
xe.innerHTML="[<b>XElement</b>]";
document.body.appendChild( xe );
CUSTOM ELEMENTS
создание нового типа расширением существующего
Расширение «нативного» элемента:
var XButton = document.registerElement('x-button', {
prototype: Object.create(HTMLButtonElement.prototype), extends: 'button'
}),
xb = new XButton(); xb.value = "<b>XButton</b>";
document.body.appendChild( xb );
Расширение «пользовательского» элемента:
var XElement = document.registerElement('x-element'),
XElementExt = document.registerElement('x-element-ext', {
prototype: Object.create(XElement.prototype)
}),
xee = new XElementExt(); xee.innerHTML="[<b>XElementExt</b>]";
document.body.appendChild( xee );
CUSTOM ELEMENTS
создание экземпляров элементов
Для custom elements применяется общепринятая техника инстанцирования экземпляров:
- тегом в разметке:
<tag-name></tag-name>
- созданием ноды:
document.createElement('tag-name')
- при помощи конструктора:
new XTag()
CUSTOM ELEMENTS
создание экземпляров пользовательских элементов
Тегом в разметке
<script>
var XElement = document.registerElement('x-element');
</script>
<x-element onclick="alert(this.outerHTML)">XElement</x-element>
XElement
CUSTOM ELEMENTS
создание экземпляров пользовательских элементов
Cозданием ноды
<div id="example"></div>
<script>
var XElement = document.registerElement('x-element');
(function(){
var xe = document.createElement('x-element');
xe.textContent = "XElement";
xe.addEventListener('click', function(e){ alert(this.outerHTML); });
document.querySelector('#example')
.appendChild( xe );
})();
</script>
CUSTOM ELEMENTS
создание экземпляров пользовательских элементов
При помощи конструктора
<div id="example"></div>
<script>
var XElement = document.registerElement('x-element');
(function(){
var xe = new XElement();
xe.textContent = "XElement";
xe.addEventListener('click', function(e){ alert(this.outerHTML); });
document.querySelector('#example')
.appendChild( xe );
})();
</script>
CUSTOM ELEMENTS
создание экземпляров расширенных элементов
Тегом в разметке
<script><!--
var XButton = document.registerElement('x-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
</script>
<button is="x-button"
onclick="alert(this.outerHTML)">x-button</button>
CUSTOM ELEMENTS
создание экземпляров расширенных элементов
Созданием ноды
<div id="example"></div>
<script>
var XButton = document.registerElement('x-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
(function(){
var xb = document.createElement('button', 'x-button');
xb.textContent = "XButton";
xb.addEventListener('click', function(e){ alert(this.outerHTML); });
document.querySelector('#example')
.appendChild( xb );
})();
</script>
CUSTOM ELEMENTS
создание экземпляров расширенных элементов
При помощи конструктора
<div id="example"></div>
<script>
var XButton = document.registerElement('x-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
(function(){
var xb = new XButton();
xb.textContent = "XButton";
xb.addEventListener('click', function(e){ alert(this.outerHTML); });
document.querySelector('#example')
.appendChild( xb );
})();
</script>
CUSTOM ELEMENTS
добавление свойств и методов
<div id="example"></div>
<script>
// создаем прототип
var YElementProto = Object.create(HTMLElement.prototype);
// добавляем метод
YElementProto.alertme = function() { alert(this.outerHTML); };
// добавляем поле
YElementProto.defaultvalue = 999;
// регистрируем элемент
var YElement=document.registerElement( 'y-element',
{prototype:YElementProto} );
(function(){
// создаем экземпляр
var ye = new YElement(); // document.createElement('y-element');
ye.textContent = "YElement";
ye.addEventListener('click', function(e){ this.alertme();
alert(this.defaultvalue); });
// добавляем в DOM
document.querySelector('#example')
.appendChild( ye );
})();
</script>
CUSTOM ELEMENTS
жизненный цикл
Спецификация позволяет отслеживать жизненный цикл custom element посредством вызова его callback-методов:
createdCallback
: создание экземпляраattachedCallback
: добавление в документdetachedCallback
: извлечение из документаattributeChangedCallback
: добавление, удаление или изменение аттрибутов
CUSTOM ELEMENTS
жизненный цикл
<button id="example">+ZElement</button>
<script>
var ZElementProto = Object.create(HTMLElement.prototype);
ZElementProto.createdCallback = function(){ alert('created: '+this); };
ZElementProto.attachedCallback = function(){ alert('attached: '+this); };
var ZElement=document.registerElement('z-element',{prototype:ZElementProto});
(function(){
document.querySelector('#example')
.onclick = function(){
var ze = new ZElement();
ze.innerHTML="[<b>ZElement</b>]";
ze.onclick=function(){ alert(this.outerHTML); };
example.appendChild(ze);
};
})();
</script>
CUSTOM ELEMENTS
инкапсуляция стилей и разметки
<div id="example"></div>
<template class="t-button">
<style> :host { background-color:#0f0; }
:host:hover { background-color:#ff0; }
:host:active { background-color:#f00; }
b { color:#00f } </style>
<content></content><shadow></shadow> / <b></b>
</template>
<script>
var TButtonTemplate = document.querySelector('template.t-button');
var TButtonPrototype = Object.create(HTMLButtonElement.prototype);
TButtonPrototype.createdCallback = function(){
this.textContent = "TButton";
this.addEventListener('click', function(e){ alert(this.outerHTML); });
var root = host.createShadowRoot(),
content = TButtonTemplate.content.cloneNode(true);
content.querySelector('b').textContent = (new Date()).toLocaleTimeString();
root.appendChild(content);
};
var TButton = document.registerElement('t-button', {
prototype: TButtonPrototype, extends: 'button'
});
(function(){ document.querySelector('#example')
.appendChild( new TButton() ); })();
</script>
/
CUSTOM ELEMENTS
применение
Уже сейчас можно использовать возможности Web Components с помощью полифилов:
- X-Tag - Web Components Custom Element Polylib (by Mozilla), (GitHub)
- Brick - UI Components for Modern Web Apps (by Mozilla), (GitHub)
- Polymer by Google
Каталоги компонент и UI-элементов на базе X-Tag и Polymer:
WEB COMPONENTS
источники
- W3C Introduction to Web Components
- W3C Introduction to Web Components (rus)
- W3C Shadow DOM
- W3C HTML Templates
- W3C HTML Imports
- W3C Custom Elements
- HTML5 Rocks / Shadow DOM 101
- HTML5 Rocks / Shadow DOM 201
- HTML5 Rocks / Shadow DOM 301
- HTML5 Rocks / HTML’s New Template Tag
- HTML5 Rocks / HTML Imports
- HTML5 Rocks / Custom Elements
- Custom Elements for Custom Applications – Web Components with Mozilla’s Brick and X-Tag