Gestisci il click sul pulsante interno, quindi invia un CustomEvent con un nome specifico dall’host del componente. La pagina che lo utilizza ascolta l’evento su quell’host e legge event.detail. Questa guida crea una demo completa da eseguire localmente nel browser su Mac e spiega quali opzioni dell’evento servono a ciascun listener.

Definisci la notifica di cui ha bisogno chi utilizza il componente

Per un componente Web JavaScript semplice, chi lo utilizza è la pagina o il componente contenitore che usa il tuo elemento personalizzato. In questo caso, l’obiettivo concreto è consentire a chi lo utilizza di reagire quando qualcuno attiva un pulsante interno, senza interrogare il DOM shadow del componente.

L’esempio usa un riquadro sintetico con l’etichetta Tile A. Il suo evento pubblico è tile-activate, che trasporta un itemId e un’count. CustomEvent fornisce il campo detail per questi dati applicativi; il nome dell’evento distingue tra maiuscole e minuscole. Il codice che lo invia e il listener devono usare esattamente lo stesso nome. Documentazione del costruttore CustomEvent.

Considera queste scelte come un piccolo contratto d’interfaccia:

  • Nome: tile-activate.
  • Significato: il pulsante interno è stato attivato e il conteggio è stato aggiornato.
  • Dati trasportati: detail.itemId identifica il riquadro; detail.count contiene il conteggio aggiornato.
  • Origine: l’host dell’elemento personalizzato invia la notifica.
  • Recapito: possono riceverla sia gli utilizzatori diretti sia i listener degli elementi antenati, anche attraverso i confini degli shadow tree contenitori.

Queste sono scelte di progettazione per questo esempio. Un evento utile descrive l’azione che interessa a chi utilizza il componente. In questo modo il componente può modificare in seguito il markup interno mantenendo la stessa interfaccia di notifica.

Crea e apri l’esempio completo sul tuo Mac

Ti servono un browser desktop aggiornato e un editor di testo semplice. Per questo esempio in un unico file non occorrono installazioni di pacchetti né passaggi di compilazione. Usa un elemento personalizzato autonomo che estende HTMLElement, il modello della piattaforma documentato nella sezione sugli elementi personalizzati dello standard HTML.

In TextEdit, scegli File > Nuovo, quindi Formato > Converti in testo semplice. Incolla il documento completo riportato di seguito. Scegli File > Salva, assegna il nome component-click.html e, se richiesto, seleziona Usa .html. Questi sono i passaggi documentati da Apple per creare un file HTML in TextEdit. Lavorare con i documenti HTML in TextEdit sul Mac.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Component click notification</title>
</head>
<body>
  <h1>Component click notification</h1>
  <section id="demo">
    <action-tile id="tile" item-id="tile-a"></action-tile>
    <p id="direct-status">Direct listener: waiting</p>
    <p id="parent-status">Parent listener: waiting</p>
  </section>

  <script>
    class ActionTile extends HTMLElement {
      constructor() {
        super();
        this._count = 0;
        this._button = null;
        this._root = this.attachShadow({ mode: "open" });
        this._handleClick = () => {
          this._count += 1;
          this.dispatchEvent(new CustomEvent("tile-activate", {
            detail: {
              itemId: this.getAttribute("item-id"),
              count: this._count
            },
            bubbles: true,
            composed: true
          }));
        };
      }

      connectedCallback() {
        if (!this._button) {
          this._button = document.createElement("button");
          this._button.type = "button";
          this._button.textContent = "Activate Tile A";
          this._root.append(this._button);
        }
        this._button.addEventListener("click", this._handleClick);
      }

      disconnectedCallback() {
        this._button.removeEventListener("click", this._handleClick);
      }
    }

    customElements.define("action-tile", ActionTile);

    const tile = document.getElementById("tile");
    const demo = document.getElementById("demo");
    const directStatus = document.getElementById("direct-status");
    const parentStatus = document.getElementById("parent-status");

    tile.addEventListener("tile-activate", (event) => {
      const { itemId, count } = event.detail;
      directStatus.textContent =
        `Direct listener: ${itemId}, activation ${count}`;
    });

    demo.addEventListener("tile-activate", (event) => {
      const { itemId, count } = event.detail;
      parentStatus.textContent =
        `Parent listener: ${itemId}, activation ${count}`;
    });
  </script>
</body>
</html>

Apri il file salvato nel browser e fai clic su Activate Tile A. Il primo risultato atteso è Direct listener: tile-a, activation 1 e Parent listener: tile-a, activation 1. Ogni attivazione successiva incrementa entrambi i conteggi visualizzati di uno. Sono risultati attesi in base all’esempio, non risultati di test dichiarati per un browser.

Nel codice si vede la sequenza importante: aggiornare lo stato del componente, creare la notifica, inviarla da this e lasciare che chi utilizza il componente aggiorni la propria interfaccia. dispatchEvent() richiama in modo sincrono i listener applicabili prima di restituire il controllo, perciò durante i rispettivi gestori chi utilizza il componente vede il conteggio aggiornato. Documentazione di dispatchEvent.

Scegli le opzioni corrette per la propagazione e i confini degli shadow tree

La destinazione dell’invio e la posizione del listener determinano quali opzioni sono importanti. Un listener collegato direttamente all’host riceve un evento inviato su quell’host anche quando bubbles è false. Un listener su un normale elemento antenato richiede la propagazione. Per attraversare un confine di uno shadow tree serve anche composed. I valori predefiniti sottostanti di bubbles, composed e cancelable sono tutti false. DOM Standard: interfaccia Event.

La seguente tabella decisionale deriva da queste regole di propagazione. Presuppone listener normali registrati senza capture e che nessun gestore interrompa la propagazione.

Punto di origine dell’evento personalizzato Punto in cui ascolta chi utilizza il componente Configurazione per quel percorso
Host del componente Lo stesso host Nessuna delle due opzioni deve essere true
Host del componente Elemento antenato normale nello stesso albero bubbles: true
Pulsante interno nello shadow tree Elemento antenato normale fuori da quello shadow root bubbles: true, composed: true
Host del componente all’interno di un altro shadow tree Elemento antenato normale fuori dallo shadow root contenitore bubbles: true, composed: true

Nella demo, this.dispatchEvent(...) invia la notifica sull’host. Di conseguenza, non è necessario che esca dallo shadow root del componente. composed: true permette di usare la notifica oltre il confine dello shadow tree di un componente contenitore nel caso in cui questo riquadro venga annidato in seguito. Questa portata più ampia dovrebbe essere una scelta consapevole dell’interfaccia pubblica. Documentazione della proprietà composed di Event.

Per un esperimento mirato, modifica solo bubbles: true in bubbles: false, salva e ricarica la pagina. All’attivazione, lo stato diretto dovrebbe aggiornarsi, mentre quello del genitore dovrebbe restare Parent listener: waiting. Ripristina la propagazione. Modificando solo composed in false, entrambi i listener dovrebbero continuare a funzionare in questo documento specifico, perché nessuno dei due percorsi attraversa un confine di uno shadow tree.

Mantieni il comportamento di inizializzazione e riconnessione

Inizialmente la consegna degli eventi può sembrare corretta, ma generare notifiche duplicate dopo che un componente viene spostato o riconnesso. Mantieni esplicite le responsabilità di creazione, connessione e pulizia.

Il costruttore chiama prima super(), inizializza lo stato e collega uno shadow root. Il rendering avviene in connectedCallback() e, grazie al riferimento esistente al pulsante, viene eseguito una sola volta. Queste scelte seguono i requisiti dello standard HTML per i costruttori e le indicazioni di proteggere l’inizializzazione che non deve ripetersi quando vengono eseguiti di nuovo i callback di connessione. Requisiti per i costruttori e le reazioni degli elementi personalizzati.

Il componente registra il listener per il clic interno quando si connette e lo rimuove quando si disconnette. Alla riconnessione riutilizza il pulsante esistente e conserva _count. I callback di connessione e disconnessione sono hook del ciclo di vita richiamati quando gli elementi entrano o escono dal documento. Uso degli elementi personalizzati.

La funzione _handleClick memorizzata è importante: per rimuovere il listener bisogna specificare la stessa funzione usata in fase di registrazione, con la stessa impostazione di capture. Creare una nuova funzione freccia nella chiamata di rimozione non identificherebbe il listener registrato. Documentazione di removeEventListener.

Se in un componente più grande aggiungi listener a window, document o a un’altra destinazione esterna, assicurati di gestirne la pulizia con altrettanta chiarezza. Tieni separate le modifiche al ciclo di vita dal significato della notifica, in modo che la riconnessione dell’elemento non annunci per errore un’azione dell’utente.

Individua le notifiche mancanti o duplicate

Prima di modificare le opzioni dell’evento, usa le due righe di stato visibili per localizzare il problema. Permettono di distinguere un problema con la notifica del componente da un problema con la consegna agli elementi antenati.

  1. Nessuno dei due stati cambia: confronta il nome dell’evento all’invio e alla registrazione, quindi verifica che la definizione dell’elemento personalizzato e il gestore del pulsante vengano eseguiti. In questo documento, i listener che utilizzano il componente vengono registrati prima di qualsiasi attivazione dell’utente.
  2. Cambia solo lo stato diretto: controlla bubbles. Verifica anche se qualche gestore intermedio interrompe la propagazione.
  3. La consegna non funziona solo quando il componente è annidato in un altro: individua il confine dello shadow tree su quel percorso e controlla composed.
  4. I conteggi aumentano a scatti dopo la riconnessione: verifica che i listener non vengano registrati più volte, soprattutto se ogni connessione dell’elemento crea nuove funzioni anonime.

Le funzioni anonime ripetute sono listener distinti, anche se hanno lo stesso testo sorgente. Mantenere un riferimento stabile alla funzione semplifica la registrazione e la pulizia. addEventListener() distingue inoltre l’opzione capture del listener, che ne determina il momento di esecuzione. Documentazione di addEventListener.

Un evento click nativo può già raggiungere l’esterno di uno shadow tree, perché i clic inviati dal browser sono composti. Un evento personalizzato separato è utile quando chi utilizza il componente ha bisogno di un’azione denominata del componente e di un payload definito. Evita di gestire entrambe le notifiche come se fossero la stessa azione applicativa, a meno che la duplicazione non sia intenzionale. Documentazione della proprietà composed di Event.

Infine, nel progettare gli eventi, distingui la notifica dall’autorizzazione a procedere. In questo esempio il conteggio viene incrementato prima dell’invio e l’evento non è annullabile. Se chi utilizza il componente chiama preventDefault(), non può annullare l’aggiornamento. Una richiesta annullabile richiederebbe un’implementazione esplicita che controlli il valore restituito da dispatchEvent() prima di eseguire l’azione richiesta. Valore restituito da dispatchEvent.

Per questo compito, il contratto completo è semplice: un gestore del pulsante interno aggiorna lo stato, un evento personalizzato con un nome specifico viene inviato dall’host e chi utilizza il componente legge i dati trasportati e documentati. Assicurati prima che funzioni il listener diretto, quindi abilita i percorsi di propagazione effettivamente necessari all’interfaccia contenitore.

Fonti