Gestire gli eventi nei componenti Web Lightning
Obiettivi di apprendimento
Al completamento di questa unità, sarai in grado di:
- Creare un'app costituita da più componenti.
- Descrivere la struttura di file di un componente complesso.
- Gestire gli eventi.
Seguire il journey di un evento
Hai creato un componente e lo hai reso disponibile in un'organizzazione. Iniziamo ad aggiungere alcune funzionalità interattive con la gestione eventi. Seguiamo il journey di un evento attraverso i vari componenti per la gestione articolata dell'evento in un'applicazione. L'applicazione è un selettore di prodotti per un negozio di biciclette. Gli utenti fanno clic sul nome e sull'immagine di una bicicletta per visualizzarne i dettagli.
In questa app abbiamo quattro componenti che interagiscono tra loro.
-
tile (riquadro): mostra il singolo articolo.
-
list (elenco): dispone i riquadri.
-
detail (dettagli): mostra i dettagli dell'articolo quando si fa clic su un riquadro (in modo analogo al componente bikeCard che hai appena creato).
-
selector (selettore): contiene l'intero set di componenti. Non è necessario un componente container (contenitore), tuttavia in questo caso ne useremo uno per facilitare la gestione degli eventi.
Per il momento, l'applicazione utilizza un file di dati per caricare dati statici a scopo di testing. Nella prossima unità imparerai a estrarre dati dinamici da un'organizzazione.
Composizione del componente
Aggiungiamo al progetto alcuni file che possiamo distribuire in un'organizzazione.
- Scaricai file dell'app da qui: Bike Selector App for Trailhead (App Selettore bici per Trailhead).
- Decomprimi i file nella cartella force-app/main/default/lwc del progetto bikeCard.
Relazioni dei componenti
In quest'app più componenti lavorano assieme; alcuni componenti sono nidificati all'interno di altri. Analogamente a quanto accade per gli elementi HTML, è possibile nidificare i componenti Web Lightning, che sono di fatto elementi HTML personalizzati, all'interno di altri componenti Web Lightning.
Nel file system non è possibile intuire dalle cartelle dei componenti quale sia la relazione tra di essi.
Usiamo un diagramma per vedere come sono nidificati i componenti a livello di interfaccia utente.
Osservando i file puoi vedere che il componente selector determina il layout della pagina e visualizza i componenti list (c-list
) e detail (c-detail
).
<template> <div class="wrapper"> <header class="header">Select a Bike</header> <section class="content"> <div class="columns"> <main class="main" > <c-list onproductselected={handleProductSelected}></c-list> </main> <aside class="sidebar-second"> <c-detail product-id={selectedProductId}></c-detail> </aside> </div> </section> </div> </template>
Aggiorna detail.html con il codice seguente:
<template> <template lwc:if={product}> <div class="container"> <div>{product.fields.Name.value}</div> <div class="price">{product.fields.MSRP__c.displayValue}</div> <div class="description">{product.fields.Description__c.value}</div> <img class="product-img" src={product.fields.Picture_URL__c.value} alt={product.fields.Name.value}/> <p> <lightning-badge label={product.fields.Material__c.value}></lightning-badge> <lightning-badge label={product.fields.Level__c.value}></lightning-badge> </p> <p> <lightning-badge label={product.fields.Category__c.value}></lightning-badge> </p> </div> </template> <template lwc:else> <div>Select a bike</div> </template> </template>
Se osservi detail.html, vedrai che il rendering è condizionale (lwc:if={product}
e lwc:else
). Se l'utente non ha selezionato nulla dall'elenco, un messaggio chiede all'utente di selezionare un articolo. Se è stata effettuata una selezione, vengono visualizzate le informazioni relative alla bici.
Il componente list esegue il rendering di più componenti tile (c-tile
), uno per ogni bici presente nei dati. Questa nidificazione viene realizzata nel codice HTML per ogni componente principale. Ad esempio, il codice HTML del componente list riportato di seguito include il componente tile come elemento c-tile
.
<template> <div class="container"> <template for:each={bikes} for:item="bike"> <c-tile key={bike.fields.Id.value} product={bike} ontileclick={handleTileClick}></c-tile> </template> </div> </template>
Come vedi, ciascuna iterazione dell'articolo bici genera un nuovo componente tile. È sufficiente includere il tag del componente c-tile
per far sì che ogni componente tile diventi un suo componente secondario. Puoi controllare la disposizione dei riquadri grazie alla definizione div class "container" utilizzata nello stile. Se guardi nel file list.css, vedrai che racchiude il contenuto.
.container { display: flex; flex-direction: row; flex-wrap: wrap; }
La relazione principale/secondario è importante non solo per la progettazione dell'app, ma anche per la gestione degli eventi.
Analizziamo più a fondo la gestione degli eventi.
Eventi verso l'alto, proprietà verso il basso
In un componente complesso (che contiene diversi componenti principali e secondari) i componenti possono comunicare verso l'alto e verso il basso.
- Il componente secondario c-todo-item invia un evento al componente principale c-todo-app. Ad esempio, quando l'utente fa clic su un pulsante, il componente secondario può passare un oggetto evento a quello principale per consentirgli di gestire l'evento e cambiare la pagina corrente.
- Il componente principale c-todo-app passa una proprietà o chiama un metodo del componente secondario. Ad esempio, il componente principale può impostare un valore di testo in un componente secondario o chiamare un metodo di quel componente.
Vediamo come funziona la comunicazione.
Passare le informazioni al livello superiore
Le informazioni possono essere trasferite al livello superiore utilizzando gli elementi event ed event listener.
Il componente secondario invia l'evento mentre il componente principale è in ascolto. L'invio dell'evento comporta la creazione di un oggetto evento che il componente secondario può passare a quello principale, che contiene un gestore per rispondere all'evento.
Ad esempio (non creare questi componenti) un componente secondario come quello seguente contiene un metodo nextHandler()
che crea un oggetto evento semplice utilizzando CustomEvent()
e invia il tipo di evento "next" quando l'utente fa clic su un pulsante Next (Avanti).
// todoItem.js import { LightningElement } from 'lwc'; ... nextHandler() { this.dispatchEvent(new CustomEvent('next')); } }
Il componente principale è in ascolto per intercettare l'evento con il gestore di eventi inline preceduto dal prefisso "on"(onnext).
<!-- todoApp.html --> <template> <c-todo-item onnext={nextHandler}></c-todo-item> </template>
Quindi passa l'oggetto evento a un gestore di eventi.
// todoApp.js import { LightningElement } from 'lwc'; export default class TodoApp extends LightningElement { ... nextHandler(){ this.page = this.page + 1; } }
Passare le informazioni al livello inferiore
Le informazioni possono essere trasferite verso il basso utilizzando public properties (proprietà pubbliche) e public methods (metodi pubblici).
Per rendere pubblica la proprietà del componente falla precedere dal decorator @api
. Quindi, imposta la proprietà pubblica a partire da un componente esterno.
Ad esempio (non creare questi componenti), se il componente secondario c-todo-item contiene il codice seguente:
// todoItem.js import { LightningElement, api } from 'lwc'; export default class TodoItem extends LightningElement { @api itemName; }
Imposta il valore a partire dal componente principale con il codice seguente:
<!-- todoApp.html --> <template> <c-todo-item item-name="Milk"></c-todo-item> </template>
Nota che la variabile itemName
viene impostata usando l'attributo item-name
che utilizza la notazione kebab-case. I nomi delle proprietà in JavaScript usano la notazione camelCase, mentre i nomi degli attributi usano quella kebab-case (separazione con trattino) per rispettare gli standard HTML. L'attributo item-name
(nome-articolo) del markup viene mappato alla proprietà JavaScript itemName
.
Le proprietà pubbliche sono un'ottima soluzione per passare valori primitivi, oggetti semplici e array.
Inoltre, puoi usare getter e setter per eseguire la logica quando le proprietà sono get (leggere il valore) o set (assegnare il valore). E ricorda di annotarle con il decorator @api
per renderle pubbliche per gli altri componenti.
Analogamente, puoi creare metodi pubblici che possono essere chiamati da un componente principale. Puoi creare un metodo pubblico nel componente secondario definendolo con il decorator @api
, quindi chiamarlo dal componente principale.
Poniamo di avere un componente secondario come il seguente (non creare questi componenti).
// videoPlayer.js import { LightningElement, api } from 'lwc'; export default class VideoPlayer extends LightningElement { @api play() { // Play music! } }
Quando il componente c-video-player è incluso in un componente principale, possiamo chiamare il metodo dal componente principale in questo modo:
// methodCaller.js import { LightningElement } from 'lwc'; export default class MethodCaller extends LightningElement { handlePlay() { this.template.querySelector('c-video-player').play(); } }
Abbiamo definito un metodo handlePlay()
che innesca l'evento. Quindi, usiamo il metodo DOM querySelector()
per cercare un elemento DOM denominato c-video-player e chiamarne il metodo pubblico.
Gestire gli eventi in HTML
La nostra app selector deve gestire un solo tipo di evento: l'utente che fa clic su un riquadro. Quando questo accade, il componente detail deve rieseguire il rendering delle informazioni dal riquadro correlato. Puoi gestire gli eventi in HTML (aggiungendo un listener di eventi al modello) o in JavaScript (scrivendo una funzione listener di eventi). È consigliabile utilizzare il codice HTML, come di seguito.
Ogni componente tile è in ascolto per intercettare il clic dell'utente, in quanto il codice HTML del componente tile (tile.html) contiene un listener di eventi onclick
.
<template> <div class="container"> <a onclick={tileClick}> <div class="title">{product.fields.Name.value}</div> <img class="product-img" src={product.fields.Picture_URL__c.value} alt={product.fields.Name.value}/> </a> </div> </template>
Quando un utente fa clic su una delle istanze del componente tile nell'interfaccia utente, il listener onclick
chiama la funzione gestore tileClick
nel file JavaScript tile.js.
import { LightningElement, api } from 'lwc'; export default class Tile extends LightningElement { @api product; tileClick() { const event = new CustomEvent('tileclick', { // detail contains only primitives detail: this.product.fields.Id.value }); // Fire the event from c-tile this.dispatchEvent(event); } }
Sequenza eventi dell'app selector
Nella nostra app per la selezione dei prodotti utilizziamo un componente complesso (che contiene diversi componenti principali e secondari). Ti consigliamo di propagare l'evento verso l'alto tramite la gerarchia dei componenti, affinché i componenti principali possano rispondere agli eventi innescati nei componenti secondari. Se hai altri componenti secondari (diversi da quello che innesca l'evento) puoi passare una proprietà al livello inferiore a quei componenti secondari in risposta all'evento.
La sequenza è illustrata di seguito:
Per farlo dobbiamo concatenare i listener e i gestori di eventi risalendo la gerarchia fino al componente ebikes, quindi passare una proprietà verso il basso al componente detail.
Ecco cosa vedi nei nostri file:
- tile.html contiene il listener di eventi
onclick
che chiama il gestoretileClick
.
- tile.js contiene il metodo
tileClick
che crea un nuovoCustomEvent
con il tipo di eventotileclick
e un oggetto che contiene un valoredetail
(this.product.fields.Id.value
).
- list.html contiene il listener
ontileclick
che chiama gestorehandleTileClick
.
- list.js contiene il metodo
handleTileClick
che passa l'evento (evt
) per creare un altroCustomEvent
(productselected
) con un oggetto che contiene anch'esso un valoredetail
,evt.detail
. Quindi, invia l'evento in JavaScript:// Fire the event from c-list this.dispatchEvent(event);
- selector.html contiene il listener di eventi
onproductselected
che chiama il gestorehandleProductSelected
.
- selector.js contiene il metodo
handleProductSelected
che impostaselectedProductId
sul valoreevt.detail
che gli era stato passato. La variabile "selectedProductId" viene passata dal componente selector al componente detail in selector.html:product-id={selectedProductId}
.
- detail.html contiene una direttiva condizionale (hai presente quelle citate nell'unità 2?) che prevede un prodotto come valore:
<template lwc:if={product}>
- detail.js mette insieme tutti i pezzi. Crea una variabile privata
_productId
per tenere traccia dello stato del valoreproductId
. Quindi utilizza uno schema get/set per leggere il valore e impostarlo su una variabileproduct
che consente a detail.html di caricare il contenuto condizionale.
I metodi getter e setter sono tipici di JavaScript. Consentono di aggiungere logica e condizioni alle assegnazioni delle proprietà.
import { LightningElement, api } from 'lwc'; import { bikes } from 'c/data'; export default class Detail extends LightningElement { product; // Private var to track @api productId _productId = undefined; // Use set and get to process the value every time it's // requested while switching between products set productId(value) { this._productId = value; this.product = bikes.find(bike => bike.fields.Id.value === value); } // getter for productId @api get productId(){ return this._productId; } }
Ogni volta che fai clic su un riquadro il processo si ripete.
Distribuire i file nell'organizzazione
Distribuiamo questi nuovi file del progetto bikeCard nell'organizzazione per vedere come funzionano. Segui la stessa procedura usata nell'unità precedente per distribuire i nuovi file: apri l'organizzazione e crea una pagina in Lightning App Builder (Generatore di app Lightning) con quest'app.
- Nel progetto bikeCard di VS Code, fai clic con il tasto destro sulla cartella force-app/main/default e seleziona SFDX: Deploy Source to Org (SFDX: distribuisci sorgente a Org).
- Dal riquadro dei comandi in VS Code, usa SFDX: Open Default Org (SFDX: apri org predefinita) per aprire l'organizzazione.
- Crea una pagina con una sola regione con il componente selector.
- Assegna l'etichetta
Your Bike Selection
(Selezione bicicletta).
- Trascina il componente selector in cima al layout di pagina.
- Salva e attiva per tutti gli utenti.
- Apri la pagina e guarda come funziona il componente nell'interfaccia utente.
Ora hai una pagina completamente interattiva costituita da vari componenti che interagiscono tra loro. Tra breve faremo qualche esperimento con lo stile e sulla ricezione di dati in tempo reale da un'organizzazione.
Risorse
- Lightning Web Components Developer Guide: Shadow DOM (Guida per gli sviluppatori di componenti Web Lightning: Shadow DOM)
- Lightning Web Components Developer Guide: Communicate with Events (Guida per gli sviluppatori di componenti Web Lightning: Comunicare con gli eventi)
- Lightning Web Components Developer Guide: Create Getters and Setters (Guida per gli sviluppatori di componenti Web Lightning: Creare getter e setter)