Kitaab

Web Components

dev javascript frontend

published 2023-04-30 11:09

updated 2023-05-02 18:40

https://developer.mozilla.org/en-US/docs/Web/API/Web_components

Web Components are a set of web technologies that make defining and re-using custom HTML elements easy. According to MDN there's three pillars upon which the standard makes this possible:

  • Custom Elements
  • Shadow DOM
  • <template> and <slot> elements

Custom Elements

Custom Elements are handled by the CustomElementRegistry object. The name is pretty self explanatory, it's a registry of your custom elements. To create a custom element you use CustomElementRegistry.define() which takes a kebab-case string as the name, a Javascript Class which defines functionality, and optionally an HTML element that it extends.

{{{javascript class WordCount extends HTMLParagraphElement { constructor() { // Always call super first in constructor super();

    const wcParent = this.parentNode;

function countWords(node){
  const text = node.innerText || node.textContent;
  return text.trim().split(/\s+/g).filter(a => a.trim().length > 0).length;
}

const count = `Words: ${countWords(wcParent)}`;

// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});

// Create text node and add word count to it
const text = document.createElement('span');
text.textContent = count;

// Append it to the shadow root
shadow.appendChild(text);

// Update count when element content changes
setInterval(function() {
  const count = `Words: ${countWords(wcParent)}`;
  text.textContent = count;
}, 200);

} }

customElements.define("word-count", WordCount, { extends: "p" }); }}}

The above is a simple example. It's also possible to define specific callbacks for the class, that get executed at specific points in the elements lifecycle.

=### Callbacks =

There are a few callbacks that can be defined for custom elements:

  • connectedCallback
    • Invoked every time the element is added into a document connected element
    • May also be called before the element has been parsed
    • May also be called once your element is no longer connected (wtf?) You can use Node.isConnected to make sure
  • disconnectedCallback
    • Invoked every time the element is disconnected from the document's DOM
  • adoptedCallback
    • Invoked every time the node is moved to a new document
  • attributeChangedCallback
    • Invoked each time one of element's attributes is added, removed or changed.

Shadow DOM

The shadow DOM is a way to enable encapsulation, allowing code to not "clash" with other custom elements, by attaching elements to a separate DOM. The shadow DOM allows elements to be attached to the regular DOM, and starts with a "shadow root" under which any elements can be attached just like the regular DOM The Shadow DOM API allows you to manipulate any Shadow DOM you attach to your custom elements

Some terminology:

  • Shadow Host
    • The regular DOM node that the shadow DOM attaches to
  • Shadow Tree
    • The DOM tree inside the Shadow DOM
  • Shadow Boundary
    • The place where the shadow DOM meets the regular DOM
  • Shadow Root
    • The root node of the shadow tree

=### Basic Usage =

The Element.attachShadow() allows you to attach a shadow root to any element. It takes as a parameter an object with mode which is either open or closed, referring to whether you can refer to the shadow root with Javascript from the "outside". Built-in HTML elements that use a Shadow DOM (such as <video>) use a closed shadow DOM, meaning that element.shadowRoot return null

An open shadow DOM has the same API as the regular DOM

{{{javascript Element.attachShadow({ "mode": "open" }) Element.attachShadow({ "mode": "closed" }) }}}

Templates and Slots

As the name kind of implies, the template tag allows creating content templates. If you have a particular set of HTML that you plan to use a bunch, it makes sense to turn it into a template. However the template tag by itself isn't very flexible. It copies exactly what's in the template. This is where slot tags come in. You can define slots in templates which you can then override each time you create an instance of your template. It's a fairly simple idea, with lots of malleability.

Let's look at an example

{{{javascript