Платформа «веб-компоненты» включает в себя несколько стандартов Web Components, которые находятся в разработке.
Начнём мы со стандарта Custom Elements, который позволяет создавать свои типы элементов.
Зачем Custom Elements?
Критично настроенный читатель скажет: «Зачем ещё стандарт для своих типов элементов? Я могу создать любой элемент и прямо сейчас! В любом из современных браузеров можно писать любой HTML, используя свои теги: <mytag>. Или создавать элементы из JavaScript при помощи document.createElement('mytag').»
Однако, по умолчанию элемент с нестандартным названием (например <mytag>) воспринимается браузером, как нечто неопределённо-непонятное. Ему соответствует класс HTMLUnknownElement, и у него нет каких-либо особых методов.
Стандарт Custom Elements позволяет описывать для новых элементов свои свойства, методы, объявлять свой DOM, подобие конструктора и многое другое.
Давайте посмотрим это на примерах.
Так как спецификация не окончательна, то для запуска примеров рекомендуется использовать Google Chrome, лучше – последнюю сборку Chrome Canary, в которой, как правило, отражены последние изменения.
Новый элемент
Для описания нового элемента используется вызов document.registerElement(имя, { prototype: прототип }).
Здесь:
имя– имя нового тега, например"mega-select". Оно обязано содержать дефис"-". Спецификация требует дефис, чтобы избежать в будущем конфликтов со стандартными элементами HTML. Нельзя создать элементtimerилиmyTimer– будет ошибка.прототип– объект-прототип для нового элемента, он должен наследовать отHTMLElement, чтобы у элемента были стандартные свойства и методы.
Вот, к примеру, новый элемент <my-timer>:
<script>
// прототип с методами для нового элемента
var MyTimerProto = Object.create(HTMLElement.prototype);
MyTimerProto.tick = function() { // свой метод tick
this.innerHTML++;
};
// регистрируем новый элемент в браузере
document.registerElement("my-timer", {
prototype: MyTimerProto
});
</script>
<!-- теперь используем новый элемент -->
<my-timer id="timer">0</my-timer>
<script>
// вызовем метод tick() на элементе
setInterval(function() {
timer.tick();
}, 1000);
</script>
Использовать новый элемент в HTML можно и до его объявления через registerElement.
Для этого в браузере предусмотрен специальный режим «обновления» существующих элементов.
Если браузер видит элемент с неизвестным именем, в котором есть дефис - (такие элементы называются «unresolved»), то:
- Он ставит такому элементу специальный CSS-псевдокласс
:unresolved, для того, чтобы через CSS можно было показать, что он ещё «не подгрузился». - При вызове
registerElementтакие элементы автоматически обновятся до нужного класса.
В примере ниже регистрация элемента происходит через 2 секунды после его появления в разметке:
<style>
/* стиль для :unresolved элемента (до регистрации) */
hello-world:unresolved {
color: white;
}
hello-world {
transition: color 3s;
}
</style>
<hello-world id="hello">Hello, world!</hello-world>
<script>
// регистрация произойдёт через 2 сек
setTimeout(function() {
document.registerElement("hello-world", {
prototype: {
__proto__: HTMLElement.prototype,
sayHi: function() { alert('Привет!'); }
}
});
// у нового типа элементов есть метод sayHi
hello.sayHi();
}, 2000);
</script>
Можно создавать такие элементы и в JavaScript – обычным вызовом createElement:
var timer = document.createElement('my-timer');
Расширение встроенных элементов
Выше мы видели пример создания элемента на основе базового HTMLElement. Но можно расширить и другие, более конкретные HTML-элементы.
Для расширения встроенных элементов у registerElement предусмотрен параметр extends, в котором можно задать, какой тег мы расширяем.
Например, кнопку:
<script>
var MyTimerProto = Object.create(HTMLButtonElement.prototype);
MyTimerProto.tick = function() {
this.innerHTML++;
};
document.registerElement("my-timer", {
prototype: MyTimerProto,
extends: 'button'
});
</script>
<button is="my-timer" id="timer">0</button>
<script>
setInterval(function() {
timer.tick();
}, 1000);
timer.onclick = function() {
alert("Текущее значение: " + this.innerHTML);
};
</script>
Важные детали:
- Прототип теперь наследует не от
HTMLElement, а отHTMLButtonElement - Чтобы расширить элемент, нужно унаследовать прототип от его класса.
- В HTML указывается при помощи атрибута
is="..." - Это принципиальное отличие разметки от обычного объявления без
extends. Теперь<my-timer>работать не будет, нужно использовать исходный тег иis. - Работают методы, стили и события кнопки.
- При клике на кнопку её не отличишь от встроенной. И всё же, это новый элемент, со своими методами, в данном случае
tick.
При создании нового элемента в JS, если используется extends, необходимо указать и исходный тег в том числе:
var timer = document.createElement("button", "my-timer");
Жизненный цикл
В прототипе своего элемента мы можем задать специальные методы, которые будут вызываться при создании, добавлении и удалении элемента из DOM:
createdCallback Элемент созданattachedCallback Элемент добавлен в документdetachedCallback Элемент удалён из документаattributeChangedCallback(name, prevValue, newValue) Атрибут добавлен, изменён или удалёнКак вы, наверняка, заметили, createdCallback является подобием конструктора. Он вызывается только при создании элемента, поэтому всю дополнительную инициализацию имеет смысл описывать в нём.
Давайте используем createdCallback, чтобы инициализировать таймер, а attachedCallback – чтобы автоматически запускать таймер при вставке в документ:
<script>
var MyTimerProto = Object.create(HTMLElement.prototype);
MyTimerProto.tick = function() {
this.timer++;
this.innerHTML = this.timer;
};
MyTimerProto.createdCallback = function() {
this.timer = 0;
};
MyTimerProto.attachedCallback = function() {
setInterval(this.tick.bind(this), 1000);
};
document.registerElement("my-timer", {
prototype: MyTimerProto
});
</script>
<my-timer id="timer">0</my-timer>
Итого
Мы рассмотрели, как создавать свои DOM-элементы при помощи стандарта Custom Elements.
Далее мы перейдём к изучению дополнительных возможностей по работе с DOM.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen...)