Gestionar eventos en Lightning Web Components
Objetivos de aprendizaje
Después de completar esta unidad, podrá:
- Crear una aplicación que incluya varios componentes.
- Describir la estructura de archivos de un componente complejo.
- Gestionar eventos.
Seguir el recorrido de un evento
Ha creado un componente y lo ha integrado en una organización. Vamos a empezar a agregar opciones de interactividad con la gestión de eventos. Seguimos el recorrido de un evento mediante varios componentes para gestionar eventos de forma sofisticada en una aplicación. Esta aplicación es un selector de productos de una tienda de bicicletas. Los usuarios hacen clic en el nombre y la imagen de una bicicleta para ver más información.
Esta aplicación tiene cuatro componentes.
-
tile (icono): muestra un artículo individual.
-
list (lista): enumera los distintos iconos.
-
detail (detalles): muestra detalles de un artículo cuando se selecciona un icono (de forma similar a la bikeCard que acaba de crear).
-
selector (selector): contiene todo el conjunto de componentes. No es necesario usar un componente de contenedor, pero aquí usamos uno para que ayude con la gestión de eventos.
Por ahora, la aplicación usa un archivo de datos para cargar datos estáticos con fines de pruebas. En la siguiente unidad, aprenderá a extraer datos dinámicos de una organización.
Composición de componentes
Vamos a agregar algunos archivos a nuestro proyecto, que podremos implementar más tarde en una organización.
- Descargue los archivos para esta aplicación aquí: Aplicación Bike Selector para Trailhead.
- Descomprima los archivos en la carpeta force-app/main/default/lwc del proyecto bikeCard.
Relaciones entre componentes
En esta aplicación, se combinan varios componentes, algunos de los cuales se encuentra anidados dentro de otros. Al igual que anida elementos HTML dentro de otros, puede anidar componentes web Lightning (que son elementos HTML personalizados) dentro de otros componentes web Lightning.
En nuestro sistema de archivos, las carpetas de componentes no muestran las relaciones entre ellos.
Veamos cómo se anidan los componentes a nivel de interfaz de usuario en el siguiente diagrama.
Si mira los archivos, verá que el componente del selector organiza la página y representa los componentes list(c-list
) y 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>
Actualice el archivo detail.html con lo siguiente:
<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>
Si observa detail.html, verá el condicional rendering(lwc:if={product}
and lwc:else
). Si no se ha elegido nada en la lista, lo que aparece es un mensaje que pida al usuario que haga una selección. Si se ha elegido un producto, se muestra la información de la bicicleta.
El componente de lista reproduce varios componentes de tipo tile(c-tile
), uno por cada bicicleta de los datos. Esta anidación tiene lugar en el archivo HTML de cada componente principal. Por ejemplo, el componente list (lista) tiene el siguiente código HTML, que incluye el componente tile (icono) como 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>
Observe cómo cada iteración de artículo de bicicleta genera un nuevo componente tile (icono). El simple hecho de incluir la etiqueta de componente c-tile
hace que cada componente tile (icono) sea su elemento secundario. La definición de clase div "container" se utiliza para aplicar estilo, de forma que pueda controlar la disposición de los iconos. Si abre el archivo list.css, verá que resume todo el contenido.
.container { display: flex; flex-direction: row; flex-wrap: wrap; }
La relación principal/secundario es importante, no solo para el diseño de la aplicación, sino para la gestión de eventos.
Profundicemos un poco más en la gestión de eventos
Comunicación ascendente de eventos y descendente de propiedades
En un componente complejo (uno que contenga varios componentes principales y secundarios), los componentes pueden comunicarse de forma ascendente o descendente.
- El componente secundario c-todo-item envía un evento al componente principal c-todo-app. Por ejemplo, el componente secundario puede pasar un objeto de evento al principal cuando un usuario hace clic en un botón para que el principal pueda gestionar el evento y cambiar la página actual.
- El componente principal c-todo-app pasa una propiedad o invoca un método en el componente secundario. Por ejemplo, el componente principal puede definir un valor de texto en un componente secundario, o bien invocar un método en este.
Veamos cómo funciona esta comunicación.
Comunicación ascendente de información
Podemos comunicar información hacia arriba mediante eventos y agentes de escucha de eventos.
El componente secundario envía el evento y el principal lo escucha. El envío del evento incluye la creación de un objeto de evento que el componente secundario puede pasar al principal. El componente principal contiene un controlador para responder al evento.
Por ejemplo (no cree estos componentes), un componente secundario como este contiene un método nextHandler()
que crea un objeto de evento sencillo mediante CustomEvent()
y envía el tipo de evento "next" (siguiente) cuando el usuario hace clic en el botón Next (Siguiente).
// todoItem.js import { LightningElement } from 'lwc'; ... nextHandler() { this.dispatchEvent(new CustomEvent('next')); } }
El componente principal escucha el evento con el controlador de eventos en línea prefijado con 'on'(onnext).
<!-- todoApp.html --> <template> <c-todo-item onnext={nextHandler}></c-todo-item> </template>
Después, pasa el objeto de evento al controlador de eventos.
// todoApp.js import { LightningElement } from 'lwc'; export default class TodoApp extends LightningElement { ... nextHandler(){ this.page = this.page + 1; } }
Comunicación descendente de información
La información se puede comunicar en sentido descendente mediante propiedades públicas y métodos públicos.
Para hacer que una propiedad de componente sea pública, puede agregarle delante el decorador @api
. A continuación, defina la propiedad pública con un componente externo.
Por ejemplo (no cree estos componentes), si el componente secundario c-todo-item contiene lo siguiente:
// todoItem.js import { LightningElement, api } from 'lwc'; export default class TodoItem extends LightningElement { @api itemName; }
Defina el valor del principal con lo siguiente:
<!-- todoApp.html --> <template> <c-todo-item item-name="Milk"></c-todo-item> </template>
Observe que la variable itemName
se define mediante el atributo con el tipo de convención kebab item-name
. Los nombres de propiedades en JavaScript se escriben con una combinación de mayúsculas y minúsculas, mientras que los de atributos HTML se escriben con la convención kebab (separados por guiones) para que coincida con los estándares de HTML. El atributo item-name
del marcado se asigna a la propiedad de JavaScript itemName
.
Las propiedades públicas son una buena solución para comunicar en sentido descendente valores primitivos, objetos simples y matrices.
Además, puede usar métodos getter y setter para ejecutar lógica al obtener o definir propiedades. Recuerde anotarlas con el decorador @api
para hacerlas públicas para otros componentes.
De forma similar, puede crear métodos públicos a los que se pueda llamar desde un componente principal. Cree un método público en el componente secundario. Para ello, defínalo con el decorador @api
y luego llámelo desde el componente principal.
Supongamos que tenemos un componente secundario como este (no cree estos componentes).
// videoPlayer.js import { LightningElement, api } from 'lwc'; export default class VideoPlayer extends LightningElement { @api play() { // Play music! } }
Cuando se incluye el componente c-video-player en un componente principal, podemos invocar el método desde el componente de esta manera:
// methodCaller.js import { LightningElement } from 'lwc'; export default class MethodCaller extends LightningElement { handlePlay() { this.template.querySelector('c-video-player').play(); } }
Definimos un método handlePlay()
que activa el evento. Después usamos el método DOM querySelector()
para buscar un elemento DOM denominado c-video-player e invocar este método público.
Gestión de eventos en HTML
Nuestra aplicación de selector necesita gestionar un tipo de evento: cuando el usuario hace clic en un icono. Cuando esto ocurra, el componente detail (detalles) debe volver a representarse con la información del icono relacionado. Puede gestionar eventos en HTML (agregando un agente de escucha de eventos en la plantilla) o JavaScript (escribiendo una función de agente de escucha de eventos). Recomendamos usar el enfoque de HTML de la siguiente manera.
Cada componente tile (icono) escucha el clic del usuario, puesto que el archivo HTML del componente (tile.html) contiene un agente de escucha de eventos 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>
Cuando un usuario hace clic en uno de los iconos de la interfaz de usuario, el agente de escucha onclick
llama a la función del controlador tileClick
del archivo 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); } }
Patrón de evento en la aplicación Selector
En nuestra aplicación de selector de productos, usamos un componente complejo (uno que contiene varios componentes principales y secundarios). Recomendamos propagar el evento en sentido ascendente por toda la jerarquía de componentes para que los componentes principales puedan responder a los eventos de los secundarios. Si tiene otros componentes secundarios (no el que desencadena el evento), puede pasar una propiedad hacia abajo a esos componentes en respuesta al evento.
El patrón tiene el siguiente aspecto:
Para hacerlo, necesitamos encadenar agentes de escucha de eventos y controladores en la jerarquía hasta llegar al componente ebikes. Después, debemos pasar una propiedad hacia abajo hasta el componente detail.
En nuestros archivos, verá lo siguiente.
- tile.html contiene el agente de escucha de eventos
onclick
que llama al controladortileClick
.
- tile.js contiene el método
tileClick
que crea un nuevoCustomEvent
con el tipo de eventotileclick
y un objeto que contiene un valordetail
(this.product.fields.Id.value
).
- list.html contiene el agente de escucha
ontileclick
que llama al controladorhandleTileClick
.
- list.js contiene el método
handleTileClick
que pasa el evento(evt
) para crear otro eventoCustomEvent
(productselected
) con un objeto que también contiene un valordetail
evt.detail
. Después, se envía el evento en JavaScript:// Fire the event from c-list this.dispatchEvent(event);
- selector.html contiene el agente de escucha de eventos
onproductselected
que llama al controladorhandleProductSelected
.
- selector.js contiene el método
handleProductSelected
conselectedProductId
definido con el valorevt.detail
que pasamos anteriormente. La variable "selectedProductId" se pasa desde el componente selector hasta el componente detail en selector.htm:product-id={selectedProductId}
.
- detail.html contiene una directiva condicional (¿las recuerda de la Unidad 2?) que espera un valor de producto:
<template lwc:if={product}>
- detail.js une todas las partes. Crea una variable privada
_productId
para rastrear el estado del valor deproductId
. Después, usa un patrón get/set para obtener el valor y establecerlo en una variableproduct
que permita al archivo detail.html cargar el contenido condicional.
Los elementos getter y setter son una construcción típica de JavaScript. Permiten agregar lógica y condiciones a asignaciones de propiedades.
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; } }
Cada vez que hace clic en un icono, se repite este mismo proceso.
Implementar los archivos en su organización
Vamos a implementar estos nuevos archivos del proyecto bikeCard en su organización para ver cómo funciona todo. Siguiendo los mismos pasos de la última unidad, implemente los archivos nuevos, abra la organización y cree una página en Lightning App Builder con esta aplicación.
- En el proyecto bikeCard de VS Code, haga clic con el botón derecho en la carpeta force-app/main/default y seleccione SFDX: Deploy Source to Org (SFDX: Implementar código fuente en la organización).
- En Command Palette (Paleta de comandos) en VS Code, use la opción SFDX: Open Default Org (Abrir organización predeterminada) para abrir la organización.
- Cree una página de una región con el componente selector (selector).
- Asígnele la etiqueta
Your Bike Selection
(Su selección de bicicleta).
- Arrastre el componente selector (selector) a la parte superior del formato de página.
- Guarde y active el componente para todos los usuarios.
- Ábralo y vea cómo funciona en la interfaz de usuario.
Ahora tiene una página completamente interactiva compuesta por varios componentes que funcionan en conjunto. A continuación, vamos a experimentar con la aplicación de estilo y la obtención de datos en tiempo real de una organización.
Recursos
- Guía del desarrollador de componentes web Lightning: Shadow DOM
- Guía del desarrollador de componentes web Lightning: Communicate with Events
- Guía del desarrollador de componentes web Lightning: Create Getters and Setters