Introduction aux Web components

Introduction aux Web components

Les composants Web (Web component) regroupent plusieurs technologies permettant de créer des balises HTML personnalisées et réutilisables.

Dans cet article, je vais présenter les points d’entrée de la création de balises HTML personnalisées.

Nous allons créer une balise HTML paragraph-component qui représente un paragraphe assorti d’un titre, le tout entouré d’une bordure (Cela n’a aucun intérêt et c’est moche, c’est juste pour la démo de ce tutoriel ^^):

<paragraph-component data-title="Intro aux Web Components">Contenu de mon paragraphe!</paragraph-component>

Le rendu de notre balise HTML:

Contenu de mon paragraphe!

Les composants Web, c’est quoi?

Les composants web permettent de créer des composants d’interface graphique réutilisables (sans framework), ils reposent sur plusieurs technologies:

  • Custom Element: Un ensemble d’API qui permet de créer de nouvelles balises HTML ou d’étendre les balises HTML standards.
  • Shadow Dom: L’API Shadow DOM est un moyen d’encapsuler le DOM d’un élément.
    Lorsque l’on crée un Custom Element en utilisant le Shadow Dom, son DOM est « caché » et ne rentre pas en conflit avec celui de la page.
    Le DOM de l’élément ne fait pas partie de la page, un appel à document.querySelector ne va pas le retourner.
  • HTML template: Cela permet de créer des squelettes / modèles qui ne sont pas affichés sur la page. Ils peuvent être utilisés comme une base pour créer des éléments.

On peut utiliser nos balises personnalisées dans nos projets comme si elles étaient des balises HTML standards (Il n’y a qu’un fichier js à inclure).

Créer un custom element

C’est vraiment simple de créer un Custom Element, deux lignes de code suffisent:

class ParagraphComponent extends HTMLElement {}
customElements.define('paragraph-component', ParagraphComponent);

Petites explications:

  • customElements.define: Cette méthode permet de créer un Custom Element. Elle prend 3 paramètres: Le nom du Custom Element, le constructeur pour créer ce Custom Element, et enfin les options.
  • extends HTMLElement: Le fait d’étendre la classe HTMLElement permet d’hériter de toute l’API DOM. En gros, cela permet que chaque propriété et méthode que vous allez ajouter à votre classe deviendra une partie de l’interface DOM de votre élément. C’est magique 🙂
  • paragraph-component: C’est le nom de notre balise HTML. Le nom doit impérativement contenir un tiret, ceci permet au parseur HTML de différencier les éléments standards des Custom Elements.

Et voilà, on peut maintenant utiliser notre Custom Element:

<paragraph-component></paragraph-component>

On peut également instancier notre custom element via JavaScript:

<script>
const myParagraph = document.createElement('paragraph-component');

// Ajoute un paragraphe à ma page
document.body.appendChild(myParagraph);

Les événements

Les Custom Elements ont des fonctions de rappel exécutées aux différentes étapes de leur cycle de vie :

CallbackEtape de son exécution
constructorLors de la création de l’élément.
connectedCallbackExécuté à chaque fois que l’élément est inséré dans le DOM.
disconnectedCallbackExécuté à chaque fois que l’élément est retiré dans le DOM.
attributeChangedCallbackExécuté quand un attribut a été mis à jour (ajout, modification, suppression).
Cette fonction de rappel est exécutée seulement pour les attributs listés dans la propriété observedAttributes.
adoptedCallbackExécuté quand le custom element est déplacé dans un document (document.adoptNode).

Les attributs

La méthode getAttribute permet de récupérer la valeur d’un attribut:

    // Getter pour récupérer l'attribut data-title
    get title() {
        return this.getAttribute('data-title');
    }

Il est possible d’être notifié lorsque la valeur d’un attribut change. Il faut lister les attributs pour lesquels on veut recevoir une notification dans la propriété observedAttributes:

  static get observedAttributes() {
        // Notification lorsque l'attribut data-title change de valeur
        return ['data-title'];
    }

C’est la méthode attributeChangedCallback qui est exécutée à chaque changement de valeurs des attributs listés par observedAttributes:

    attributeChangedCallback(attrName, oldVal, newVal) {
        if (attrName == 'data-title') {
       ...
       }
    }

Le Shadow dom

Le Shadow DOM permet d’encapsuler le CSS et le DOM de notre custom Element, ainsi ils ne rentreront pas en conflit avec ceux de la page.

Le DOM interne de notre balise ne sera pas accessible avec document.querySelector.

La méthode attachShadow attache une arborescence Shadow DOM à notre Custom Element, et retourne une référence au noeud root:

    constructor() {
        super();
        this.root = this.attachShadow({mode: 'closed'
});
    }

Le noeud root est un élément classique, on peut ensuite lui ajouter des éléments avec la méthode appendChild.

La balise <template>

La balise <template> permet de créer une sorte de modèle qui n’est pas affiché sur la page. Cet élément peut ensuite être instancié dans du code JavaScript.

<template id="myTemplate">
  <style>
    <!-- Style CSS de notre template -->
    ...
  </style>

  <!-- Structure HTML du template-->
  ...
</template>

Le template peut être instancié en JavaScript:

// On récupère le template
const myTemplate = document.getElementById('myTemplate');
// On clone le template
const content = myTemplate.content.cloneNode(true);

...

// On ajoute ensuite notre template au shadow root
this.root.appendChild(content);

La balise <slot>

La balise <slot> représente un emplacement d’un composant web, et elle peut être remplie avec n’importe structure HTML. Nous allons nous en servir pour afficher le contenu du paragraphe.

On va donc utiliser cette balise dans notre template:

<template id="myTemplate">
  <style>

  <!-- :host est un sélecteur CSS. Il permet sélectionner un Custom Element de depuis l'intérieur du shadow DOM -->
  :host div {
      border: 1px solid black;
  }
  :host span {
        text-align: center;
        font-size: 3em;
  }
  </style>
  <div>
    <span id="title"></span>

    <!-- Slot va contenir la structure HTML du Custom Element -->
    <p><slot></slot></p>
  </div>

Le code complet de notre Custom Element

Ci-dessous le code complet de notre Custom Element paragraph-component:

// On crée notre template directement en JavaScript
let tmpl = document.createElement('template');
// Nous ajoutons le title dans le template
tmpl.innerHTML = `
  <style>
  :host div {
      border: 1px solid black;
  }
  :host span {
        text-align: center;
        font-size: 3em;
  }
  </style>
  <div>
    <span id="title"></span>
    <p><slot></slot></p>
  </div>
`;

class ParagraphComponent extends HTMLElement {
    static get observedAttributes() {
        // On indique de recevoir une notification quand l'attribut data-title change de valeur
        return ['data-title'];
    }
    constructor() {
        super();
        // Dans le constructeur, on ne fait qu'indiquer d'utiliser shadow DOM
        // On ne gère pas encore le rendu 
        this.root = this.attachShadow({
            mode: 'open'
        });
    }

    // Getter pour récupérer l'attribut data-title
    get title() {
        return this.getAttribute('data-title');
    }

    // On génère le rendu dès que notre balise est ajoutée au DOM
    connectedCallback() {
        this.render();
    }

    // On met à jour le rendu quand le title change
    attributeChangedCallback(attrName, oldVal, newVal) {
        this.render();
    }

    // Méthode qui génère le rendu
    render() {
        // Clone le template
        const content = tmpl.content.cloneNode(true);
        // Ajoute le title
        const title = content.getElementById("title");
        title.textContent = this.title;
        // On re-initialise le contenu de notre balise (plusieurs appels à render)
        while (this.root.firstChild) this.root.removeChild(this.root.firstChild);

        this.root.appendChild(content);
    }
}

window.customElements.define('paragraph-component', ParagraphComponent);


Étendre un élément existant

Les composants Web nous permettent également d’étendre les balises HTML standards, ainsi que les custom Elements!

On peut facilement étendre notre paragraphe:

class SuperParagraphComponent extends ParagraphComponent {
}
window.customElements.define('super-paragraph-component', SuperParagraphComponent );

Conclusion

Voilà, nous avons vu ensemble les points d’entrées pour pouvoir créer des balises HTML personnalisées. Ce tutoriel n’est qu’une introduction, il y a pas mal de points que l’on a survolé, et qui méritent d’être approfondis!

Il est à noter que l’on n’est pas obligé de créer des Custom Elements en vanilla (pur JavaScript), il est possible de les créer avec Angular, Polymer Project, …

Vous pouvez retrouver le code de ce tutoriel le sur codepen.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *