Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Estendere i Web component

Oltre a crearne di personalizzati, è anche possibile estendere web components precedentemente sviluppati, nonché estendere elementi HTML di base: ecco come.
Oltre a crearne di personalizzati, è anche possibile estendere web components precedentemente sviluppati, nonché estendere elementi HTML di base: ecco come.
Link copiato negli appunti

Abbiamo avuto modo di vedere, nel corso di questa guida, come realizzare un
Web component e come gestire le varie problematiche relative
all'incapsulamento dei dettagli interni del componente stesso. Una volta
definito un Web component, il suo utilizzo risulta abbastanza semplice:
possiamo trattarlo come se fosse un elemento HTML nativo e in linea di
massima possiamo utilizzarlo all'interno di un qualsiasi framework UI.

Ma la versatilità dei Web component non si ferma qui. Oltre ad utilizzare
il componente così com'è, via HTML e/o via JavaScript, possiamo
personalizzare alcuni comportamenti senza modificare il codice originario
tramite l'estensione o ereditarietà. Infatti, essendo un Web component una
classe JavaScript, possiamo creare una nostra classe derivata e modificare
il comportamento predefinito della classe base. Vediamo come fare con un
semplice esempio.

Supponiamo di voler creare una variante del componente di valutazione
creato finora, che chiede conferma del giudizio che si sta per dare tramite
un alert.

Supponiamo di partire da una pagina HTML come quella mostrata di seguito:

<!DOCTYPE html>
<html>
    <head>
        <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-loader.js"></script>
        <script src="myRating.js"></script>
    </head>
<body>
</body>
</html>

Inseriamo un tag <script> dopo l'inclusione del nostro Web component, e definiamo una classe
ConfirmRating che estende la classe MyRating, come nel seguente esempio:

<!DOCTYPE html>
<html>
    <head>
        <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.0.0/webcomponents-loader.js"></script>
        <script src="myRating.js"></script>
        <script>
        class ConfirmRating extends MyRating {
          constructor() {
            super();
          }
        }
        customElements.define("confirm-rating", ConfirmRating);
        </script>
    </head>
<body>
</body>
</html>

Come possiamo vedere, abbiamo semplicemente applicato le regole sintattiche dell'ereditarietà di JavaScript per definire la classe ConfirmRating in termini di MyRating, ed abbiamo poi definito l'elemento <confirm-rating> tramite customElements.define(). Naturalmente, fin qui non abbiamo fatto altro che creare un clone di MyRating. Implementiamo quindi il comportamento speciale che vogliamo assegnare a questo componente derivato, aggiungendo un gestore dell'evento click:

class ConfirmRating extends MyRating {
          constructor() {
            super();
          }
          connectedCallback() {
            this.addEventListener("click", (e) => {
              if (!confirm("Confermi la tua valutazione?")) {
                e.stopPropagation();
              }
            });
          }
        }
        customElements.define("confirm-rating", ConfirmRating);

Come possiamo vedere, abbiamo definito il metodo connectedCallback() all'interno del quale abbiamo aggiunto il gestore per l'evento clic. Il compito di questo gestore è di visualizzare un alert per chiedere conferma della valutazione che l'utente sta per fare. In caso di mancata conferma la propagazione dell'evento viene bloccata in modo che il numero di stelle evidenziate rimanga invariato. La seguente immagine dà un'idea del risultato:

Purtroppo, provando ad effettuare un test notiamo che l'alert viene
mostrato correttamente, ma se decidiamo di non confermare, l'evento non
viene bloccato come ci aspettavamo ed il numero di stelle evidenziate
cambia.

Il motivo di questo comportamento non è legato ai Web component, ma alla
gestione standard del flusso di eventi del DOM. In particolare, nel nostro caso abbiamo aggiunto il gestore dell'evento
clic all'intero componente, mentre il target del clic è l'elemento <span> che implementa la stella selezionata, contenuto
all'interno del componente. Il comportamento standard del flusso di eventi
prevede che i gestori di evento vengano coinvolti nella fase di bubbling dell'evento, cioè dopo che l'evento ha raggiunto il target e risale la gerarchia del DOM. Ma questo vuol dire che
ormai l'evento clic è stato catturato dall'elemento <span> e
le stelle che indicano la valutazione dell'utente sono già state
evidenziate. In altre parole, non abbiamo più modo di annullare l’effetto
dell'evento clic.

Per impedire ciò dobbiamo catturare l'evento prima che arrivi al target, cioè nella fase di capturing. La cosa può essere
fatta in maniera molto semplice aggiungendo il valore true come
terzo parametro del metodo addEventListener().
Il codice del metodo connectedCallback() sarà
quindi quello mostrato di seguito:

connectedCallback() {
  this.addEventListener("click", (e) => {
    if (!confirm("Confermi la tua valutazione?")) {
      e.stopPropagation();
    }
  }, true);
}

A questo punto otterremo il comportamento desiderato.

Estensione di elementi HTML nativi

Oltre ad estendere i Web component basati sul concetto di custom element, possiamo estendere anche gli elementi HTML nativi, come <div>,
<button>, <a>, ecc. L'estensione di un elemento HTML nativo può risultare molto utile
per evitare di implementare da zero funzionalità comuni già supportate.
Supponiamo, ad esempio, di voler creare un Web component che ha l'aspetto
di un normale link, ma che in corrispondenza della sua selezione avvisa
l'utente se si sta navigando verso un sito che non supporta HTTPS. Se
decidiamo di implementare il componente come custom element, possiamo partire con la definizione di una classe ed un template come nel
seguente esempio:

const linkTemplate = document.createElement('template');
linkTemplate.innerHTML = "<a href='#'>secure link</a>";
class SecureLink extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
    this.shadowRoot.appendChild(linkTemplate.content.cloneNode(true))
  }
}
customElements.define("secure-link", SecureLink);

Come possiamo vedere, abbiamo definito un template utilizzato all’interno
della classe SecureLink, ed abbiamo registrato la classe come custom element. Naturalmente a questo stadio dell’implementazione
il link non è funzionante come ci siamo proposti, anzi non è nemmeno
funzionante come un normale link HTML. Dobbiamo ancora implementare almeno
la possibilità di specificare da parte dell’utente il link verso cui
navigare ed il testo da visualizzare, prima di personalizzare il suo
funzionamento. In breve, se scegliamo la strada di implementare il nostro
link come custom element, dobbiamo scrivere un bel po’ di codice
prima di ottenere un link con il comportamento standard dei link HTML. E
questo senza contare il fatto che in ogni caso avremmo un componente che è
cosa diversa da un link HTML, a cominciare dal fatto che ha un tag
completamente diverso da <a>. Questo aspetto porterebbe con
sé alcuni problemi, da un punto di vista concettuale diametralmente opposti
a quelli che ci hanno portato ad adottare lo shadow DOM. Ad
esempio, il nostro componente non verrebbe formattato dalle regole CSS
pensate per tutti i link di una pagina HTML, ma avrebbe bisogno di regole
specifiche. Analogamente, eventuali script che accedono a tutti i link di
una pagina, non catturerebbero il nostro componente. In altre parole, il
nostro componente apparirebbe come un link, ma in realtà non è un link.

Come possiamo ovviare alle problematiche che abbiamo esposto?

La risposta è semplice. Se vogliamo che il nostro componente venga trattato
come un normale link, invece di creare un custom element,
dobbiamo estendere un link nativo.

Consideriamo il seguente codice:

class SecureLink extends HTMLAnchorElement {
  connectedCallback() {
    this.addEventListener("click", (e) => {
      if (!this.href.startsWith("https")) {
        if (!confirm("Stai per navigare verso un sito non sicuro. Vuoi continuare?")) {
          e.preventDefault();
        }
      }
    })
  }
}
customElements.define("secure-link", SecureLink, {extends: "a"});

Questo codice implementa quello che volevamo: un link che avvisa quando
stiamo per navigare verso un sito che non usa HTTPS. Come possiamo vedere,
il codice che abbiamo dovuto scrivere è molto ridotto rispetto a quello che
avremmo dovuto scrivere con l’approccio basato su custom element.
Ma vediamo quali sono gli aspetti rilevanti rispetto alla creazione di un custom element.

Innanzitutto, notiamo che la nostra classe SecureLink non estende HTMLElement, ma HTMLAnchorElement. Stiamo cioè estendendo
la classe che definisce i link HTML nativi, ereditandone automaticamente
tutte le caratteristiche. Pertanto, l’unico codice che dobbiamo scrivere
riguarda proprio il comportamento specializzato che gli vogliamo
attribuire, nel nostro caso il controllo sul protocollo dell’URL quando
l’utente seleziona il link.

Notiamo anche che la registrazione del nuovo elemento tramite customElements.define() è leggermente diversa dal solito.
Infatti, in questo caso abbiamo specificato un terzo argomento passando
l’oggetto {extends: “a”}. La domanda nasce spontanea: perché
dobbiamo specificare questo terzo argomento dal momento che abbiamo già
indicato che il nostro componente estende HTMLAnchorElement? Non
dovrebbe essere automaticamente dedotto il fatto che stiamo estendendo
l’elemento <a>? Il fatto è che molti elementi HTML nativi
condividono la stessa classe base. Ad esempio, gli elementi <q> e <blockquote> condividono la stessa
classe base HTMLQuoteElement, quindi specificare che il nostro
componente estende questa classe non sarebbe sufficiente per indicare quale
elemento HTML vogliamo estendere. L’elenco delle classi base usate dagli
elementi HTML nativi può essere recuperato dalle
specifiche dell’HTML.

Ora che abbiamo definito il nostro elemento <a>
personalizzato, possiamo usarlo nel markup della pagina HTML come nel
seguente esempio:

<a is="secure-link" href="http://www.html.it">HTML.it</a>

Come possiamo vedere, abbiamo utilizzato l’elemento nativo <a>
specificando l’attributo
is
con il nome che abbiamo registrato per il nostro componente. In questo modo
abbiamo esteso il comportamento standard del link nativo mantenendo le
caratteristiche base dell’elemento stesso. In più abbiamo il vantaggio di
mantenere la compatibilità con i browser che non supportano i Web
component. Infatti, in questo caso il link non supporterà il comportamento
aggiuntivo, ma continuerà a funzionare come un normale link.

Ti consigliamo anche