Tabla de Contenidos

Web Components

Libraries:

Basic

Concepts

Web components are those functions that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. These are the concepts that involve their creation:

Steps for creating a web component

  1. Create a class that extends from HtmlElement.
  2. Register it using CustomElementRegistry.define().
  3. If required, atach a shadow DOM Element.attachShadow().
  4. If required define templates with <template> and <slot>.
  5. Use the defined tag.

The most basic Web Component

(function() {
    class MyTitle extends HTMLElement {
      connectedCallback() {
        this.innerHTML = `
          <style>
            h1 {
              font-size: 2.5rem;
              color: hotpink;
              font-family: monospace;
              text-align: center;
              text-decoration: pink solid underline;
              text-decoration-skip: ink;
            }
          </style>
          <h1>Hello Alligator!</h1>
        `;
      }
    }
 
    window.customElements.define('my-title', MyTitle);
  })();

Now we can add the tag <my-title></my-title> to our code and a pink “Hello Alligator!” will appear.

Resources

Reference

Life-cycle and callbacks

Please, remind if you define a constructor you must call super() as the first line of the block.

Shadow DOM

Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree.

You can attach a shadow root to any element using the Element.attachShadow() method. This takes as its parameter an options object that contains one option — mode — with a value of open or closed:

let shadow = elementRef.attachShadow({mode: 'open'});
let shadow = elementRef.attachShadow({mode: 'closed'});

open means that you can access the shadow DOM using JavaScript written in the main page context, for example using the Element.shadowRoot property:

let myShadowDom = myCustomElem.shadowRoot;

If you attach a shadow root to a custom element with mode: closed set, you won't be able to access the shadow DOM from the outside — myCustomElem.shadowRoot returns null. This is the case with built in elements that contain shadow DOMs, such as <video>.

Templates

Instead of defining the HTML and CSS in a JavaScript string, you can use a template tag in HTML and assign it an id:

<template id="custom-title-template">
  <style>
    h1 {
      font-size: 7rem;
      color: #000;
      font-family: Helvetica;
      text-align: center;
    }
  </style>
  <h1>My Custom Title!</h1>
</template>
 
<custom-title></custom-title>

Then you can reference it in your Custom Element constructor and add it to the Shadow DOM:

class CustomTitle extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const tmpl = document.querySelector('#custom-title-template')
    this.shadowRoot.appendChild(tmpl.content.cloneNode(true))
  }
}
 
window.customElements.define('custom-title', CustomTitle)

Slots

Slots are identified by their name attribute, and allow you to define placeholders in your template that can be filled with any markup fragment you want when the element is used in the markup.

We would define a slot:

<p><slot name="my-text">My default text</slot></p>

Then, inside your web-component:

<my-paragraph>
  <span slot="my-text">Let's have some different text!</span>
</my-paragraph>
<!-- or -->
<my-paragraph>
  <ul slot="my-text">
    <li>Let's have some different text!</li>
    <li>In a list!</li>
  </ul>
</my-paragraph>

CSS

CSS pseudo-classes

CSS pseudo-elements

How to...

... define inline template?

var template = document.createRange().createContextualFragment( `
    <style>
    div {font-family: "Open Sans Light",Helvetica,Arial}
    <div>
      <slot name="attributes"></slot>
    </div>
    <hr>
`);
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));

... treat slots inner content?

<custom-menu id="menu">
  <span slot="title">Candy menu</span>
  <li slot="item">Lollipop</li>
  <li slot="item">Fruit Toast</li>
</custom-menu>

<script>
customElements.define('custom-menu', class extends HTMLElement {
  items = []

  connectedCallback() {
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `<div class="menu">
      <slot name="title"></slot>
      <ul><slot name="item"></slot></ul>
    </div>`;

    // slottable is added/removed/replaced
    this.shadowRoot.firstElementChild.addEventListener('slotchange', e => {
      let slot = e.target;
      if (slot.name == 'item') {
        this.items = slot.assignedElements().map(elem => elem.textContent);
        alert("Items: " + this.items);
      }
    });
  }
});

// items update after 1 second
setTimeout(() => {
  menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Cup Cake</li>')
}, 1000);
</script>