ТРЕНДЫ ФРОНТЭНДА

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) позиции отображения контента из другого дерева, а механизм распределения отображает в них указанный контент, не меняя при этом ни одно из этих деревьев.

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="">

Особенности:

  • повторные импорты отсекаются браузером
  • валидируется браузером как 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">&times;</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 с помощью полифилов:

Каталоги компонент и UI-элементов на базе X-Tag и Polymer:

WEB COMPONENTS
источники