Платформа «веб-компоненты» включает в себя несколько стандартов 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…)