Is something memorable
The custom elements API lets you create your own elements uniting a tag name to a JavaScript class. You might have discovered how already, perhaps as part of a wider introduction to web components, but it is possible to rehearse the steps quite rapidly.
Define
You start in markup, with an element with at least two words separated with a hyphen.
<something-memorable></something-memorable> In a script connected to the document, then, you initialize a class which extends the HTMLElement interface.
class SomethingMemorable extends HTMLElement {
// ...
} In the body of the class the constructor function helps to set up the element, and calling super helps to inherit the features of the base structure.
class SomethingMemorable extends HTMLElement {
constructor() {
super();
}
} You finally need to link the two concepts, and this is achieved through the mentioned API, with the customElements global and the define function.
customElements.define('something-memorable', SomethingMemorable); In goes the string used in the tags. In goes the reference to the class. Out you receive the custom element and the opportunity to enhance the markup any way you may so choose.
The logic resides in the class where you can, for instance, augment the HTML with additional content.
this.innerHTML = '<p>Thank Naminé</p>'; For the sake of enhancing the markup, however, the class opens new doors in the starting signature. Indeed, as an editor might have spoiled when typing the code, it is possible to inherit from a different interface, not just the generic HTMLElement. This is a particularly exciting option as you can build on top of existing, more capable nodes.
Let us try to recreate a blockquote element. We can extend HTMLQuoteElement.
class SomethingMemorable extends HTMLQuoteElement {
// ...constructor
} But if you are befuddled, so is the browser, although for a slightly different reason. You might argue that blockquote is not the only semantic choice to mark up a quote, so is q for inline citations — how to you tell the difference? The browser, as you run the snippet, argues differently, prompting an error with a stern message and a teaching opportunity to clear any doubt.
Looking into the specification, it is possible to distinguish between autonomous and customized built-in custom elements. In the first category you have those which extend HTMLElement, those which come as a blank slate and you author from the ground up. In the second category you have those extending standard elements. These are inherently different, and have a canonical setup.
Browsers need to know the element’s true nature, so that you need to preserve the original tag name.
<blockquote></blockquote> To upgrade the node, then, you point to the custom element with an attribute fit for the task: is.
<blockquote is="something-memorable"></blockquote> The class remains valid, and you gain the option to extend the interface designed for the node. But there’s one last adjustment for the API, which needs to know the specific element as well. For this you include a third argument in the define function, an object where the extends property isolates the chosen tag name.
customElements.define('something-memorable', SomethingMemorable, {
extends: 'blockquote'
}); There are a few tweaks for the syntax of customized built-in elements, but they are more than worth learning. No longer do you start from zero, but from an existing element, with proper semantics and understood features.
Enhance
One of the most valuable assets of custom elements, and web components in general, is that by using the platform you build on top of solid foundations. Customized built-in elements are a perfect expression of this fact. And our own little blockquote a fitting opportunity to showcase a small enhancement.
We can use the element for its intended purpose, to mark up a quote.
<blockquote is="something-memorable">
<p><!-- ...something that rhymes --></p>
</blockquote> And since the words are going to be particularly memorable, we can use the custom element to add a date. For this we can continue in the constructor function, right after the call to the super function.
In this context, this refers to the custom element — the leveled up blockquote. this.innerHTML relates the markup, so we can add the date after the existing nodes.
this.innerHTML += `<time>...</time>`; Or, append a proper element created through the document.createElement function.
const time = document.createElement('time');
this.appendChild(time); In either approach the time element is the most fitting and flexible choice. In the datetime attribute we focus on the date in a specific, robotic format.
const date = new Date();
time.setAttribute('datetime', date.toISOString()); The value is understood by browsers, which delight in the given standard.
For the content between the tags we pay more attention, more care to a nicer version, mixing the ingredients in a more readable fashion.
time.textContent = format(date); You can elaborate a format function yourself, receiving a date object as input and returning a nice label. But on the web you also have the option to rely on the Intl library. Through the DateTimeFormat module you are able to structure the label with nary a string, pointing to a locale, to a country’s own flair.
new Intl.DateTimeFormat('en-US'); And if you are particular, refine the function with additional fields.
new Intl.DateTimeFormat('en-US', {
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
}); The module returns a formatting function, saving you the hassle of creating one yourself.
const { format } = new Intl.DateTimeFormat('en-US', {
/* ...*/
}); Of course, the code works, but we can’t very well use the same date, or the current instant, for every quote. One solution is to expect a date with an attribute.
<blockquote is="something-memorable" date="2024-04-05T09:37:00">
<!-- ...something that rhymes once more -->
</blockquote> In the attribute you add a valid input for the Date constructor. And in the constructor function for the custom element, you finally extract the value with the getAttribute function.
const date = new Date(this.getAttribute('date')); A minor enhancement, granted, but one which unlocks a lot more. With the only drawback of showing its age with the passing of time.
There's a road for the taking,
if you end our world's aching