5 aprile 2021

Elementi personalizzati

Possiamo creare elementi HTML personalizzati, dichiarati e descritti tramite delle apposite classi, ognuno con i suoi metodi, proprietà, eventi e così via.

Una volta definito un elemento personalizzato, possiamo usarlo al pari di qualunque altro elemento HTML built-in.

Ciò è grandioso, essendo che il dizionario HTML è molto ricco, ma non infinito. Non ci sono <easy-tabs>, <sliding-carousel>, <beautiful-upload>…Ci basti pensare a qualunque altro tag di cui avremmo necessità.

Possiamo definirli con delle classi speciali, ed usarli come se fossero sempre stati parte dell’HTML.

Gli elementi personalizzati si dividono in due categorie:

  1. Elementi personalizzati autonomi – elementi “nuovi di zecca”, che estendono la classe astratta HTMLElement.
  2. Elementi built-in personalizzati – estendono gli elementi built-in, ad esempio un pulsante personalizzato, basato su HTMLButtonElement etc.

Prima di tutto, affrontiamo gli elementi autonomi, dopodiché ci sposteremo a quelli built-in personalizzati.

Per creare un elemento personalizzato, abbiamo bisogno di comunicare al browser una serie di dettagli relativi: come mostrarlo, cosa fare una volta che l’elemento viene aggiunto o rimosso dalla pagina, etc.

Ciò viene fatto creando una classe con dei metodi appositi. È facile, dato che ci sono pochi metodi, tutti opzionali.

Ecco uno schema predefinito per la classe, con la lista completa:

class MyElement extends HTMLElement {
  constructor() {
    super();
    // elemento creato
  }

  connectedCallback() {
    // il browser chiama questo metodo quando l'elemento viene aggiunto al documento
    // (può essere chiamato tante volte se un elemento viene raggiunto o rimosso)
  }

  disconnectedCallback() {
    // il browser chiama questo metodo quando l'elemento viene rimosso dal documento
    // (può essere chiamato tante volte se un elemento viene aggiunto o rimosso)
  }

  static get observedAttributes() {
    return [/* un array di nomi di attributi per monitorare le modifiche */];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    // chiamato quando uno degli attributi della lista precedente viene modificato
  }

  adoptedCallback() {
    // chiamato quando l'elemento viene spostato su un nuovo documento
    // (avviene in document.adoptNode, usato molto raramente)
  }

  // possono esserci altri metodi e proprietà per l'elemento
}

Dopodiché, possiamo registrare l’elemento:

// fa in modo che il browser sappia che <my-element> viene fornito dalla nostra classe
customElements.define("my-element", MyElement);

Adesso, per ogni elemento HTML con tag <my-element>, verrà creata un’istanza di MyElement, e chiamati i sopracitati metodi. Possiamo anche chiamare document.createElement('my-element') in JavaScript.

Il nome degli elementi custom devono contenere un trattino -

I nomi degli elementi personalizzati devono contenere un trattino -, ad esempio my-element e super-button sono nomi validi, al contrario di myelement che non lo è.

Ciò ci assicura l’assenza di conflitti tra i nostri elementi HTML personalizzati e quelli built-in.

Esempio: “time-formatted”

Per esempio, esiste già l’elemento <time> nell’HTML, per la data/ora. Solo non compie alcuna formattazione del dato di per sé.

Creiamo invece un elemento <time-formatted>, che visualizza l’ora in un formato che consideri la lingua:

<script>
class TimeFormatted extends HTMLElement { // (1)

  connectedCallback() {
    let date = new Date(this.getAttribute('datetime') || Date.now());

    this.innerHTML = new Intl.DateTimeFormat("default", {
      year: this.getAttribute('year') || undefined,
      month: this.getAttribute('month') || undefined,
      day: this.getAttribute('day') || undefined,
      hour: this.getAttribute('hour') || undefined,
      minute: this.getAttribute('minute') || undefined,
      second: this.getAttribute('second') || undefined,
      timeZoneName: this.getAttribute('time-zone-name') || undefined,
    }).format(date);
  }

}

customElements.define("time-formatted", TimeFormatted); // (2)
</script>

<!-- (3) -->
<time-formatted datetime="2019-12-01"
  year="numeric" month="long" day="numeric"
  hour="numeric" minute="numeric" second="numeric"
  time-zone-name="short"
></time-formatted>
  1. La classe contiene solo il metodo connectedCallback() che il browser chiama appena l’elemento <time-formatted> viene aggiunto alla pagina (o quando il parser HTML lo riconosce). Il metodo usa il formattatore built-in delle date Intl.DateTimeFormat, ben supportato dai browser, per mostrare l’ora formattata.