We can create custom HTML elements, described by our class, with its own methods and properties, events and so on.
Once a custom element is defined, we can use it on par with built-in HTML elements.
That’s great, as HTML dictionary is rich, but not infinite. There are no <easy-tabs>, <sliding-carousel>, <beautiful-upload>… Just think of any other tag we might need.
We can define them with a special class, and then use as if they were always a part of HTML.
There are two kinds of custom elements:
- Autonomous custom elements – “all-new” elements, extending the abstract
HTMLElementclass. - Customized built-in elements – extending built-in elements, like a customized button, based on
HTMLButtonElementetc.
First we’ll cover autonomous elements, and then move to customized built-in ones.
To create a custom element, we need to tell the browser several details about it: how to show it, what to do when the element is added or removed to page, etc.
That’s done by making a class with special methods. That’s easy, as there are only few methods, and all of them are optional.
Here’s a sketch with the full list:
class MyElement extends HTMLElement {
constructor() {
super();
// element created
}
connectedCallback() {
// browser calls this method when the element is added to the document
// (can be called many times if an element is repeatedly added/removed)
}
disconnectedCallback() {
// browser calls this method when the element is removed from the document
// (can be called many times if an element is repeatedly added/removed)
}
static get observedAttributes() {
return [/* array of attribute names to monitor for changes */];
}
attributeChangedCallback(name, oldValue, newValue) {
// called when one of attributes listed above is modified
}
adoptedCallback() {
// called when the element is moved to a new document
// (happens in document.adoptNode, very rarely used)
}
// there can be other element methods and properties
}
After that, we need to register the element:
// let the browser know that <my-element> is served by our new class
customElements.define("my-element", MyElement);
Now for any HTML elements with tag <my-element>, an instance of MyElement is created, and the aforementioned methods are called. We also can document.createElement('my-element') in JavaScript.
-Custom element name must have a hyphen -, e.g. my-element and super-button are valid names, but myelement is not.
That’s to ensure that there are no name conflicts between built-in and custom HTML elements.
Example: “time-formatted”
For example, there already exists <time> element in HTML, for date/time. But it doesn’t do any formatting by itself.
Let’s create <time-formatted> element that displays the time in a nice, language-aware format:
<script>
class TimeFormatted extends HTMLElement { // (1)
connectedCallback() {
let date = new Date(this.getAttribute('datetime') || Date.now());
this.innerHTML = new Intl.DateTimeFormat("default", {
year: this.getAttribute('year') || undefined,
month: this.getAttribute('month') || undefined,
day: this.getAttribute('day') || undefined,
hour: this.getAttribute('hour') || undefined,
minute: this.getAttribute('minute') || undefined,
second: this.getAttribute('second') || undefined,
timeZoneName: this.getAttribute('time-zone-name') || undefined,
}).format(date);
}
}
customElements.define("time-formatted", TimeFormatted); // (2)
</script>
<!-- (3) -->
<time-formatted datetime="2019-12-01"
year="numeric" month="long" day="numeric"
hour="numeric" minute="numeric" second="numeric"
time-zone-name="short"
></time-formatted>
- The class has only one method
connectedCallback()– the browser calls it when<time-formatted>element is added to page (or when HTML parser detects it), and it uses the built-in Intl.DateTimeFormat data formatter, well-supported across the browsers, to show a nicely formatted time. - We need to register our new element by
customElements.define(tag, class). - And then we can use it everywhere.
If the browser encounters any <time-formatted> elements before customElements.define, that’s not an error. But the element is yet unknown, just like any non-standard tag.
Such “undefined” elements can be styled with CSS selector :not(:defined).