Tratar de eventos em Componentes Web do Lightning
Objetivos de aprendizagem
Após concluir esta unidade, você estará apto a:
- Criar um aplicativo que inclua vários componentes.
- Descrever a estrutura de arquivos de um componente complexo.
- Tratar de eventos.
Seguir a jornada de um evento
Você criou um componente e enviou-o a uma organização. Vamos começar adicionando interatividade com o tratamento de eventos. Seguimos a jornada de um evento por vários componentes para ver o tratamento de eventos sofisticados em um aplicativo. Este aplicativo é um seletor de produtos de uma loja de bicicletas. Os usuários clicam no nome e na imagem de uma bicicleta para ver mais detalhes.
Este aplicativo tem quatro componentes trabalhando em conjunto.
-
tile: exibe um item individual.
-
list: organiza os blocos.
-
detail: exibe detalhes do item quando um bloco é clicado (semelhante ao bikeCard que você acabou de criar).
-
selector: contém todo o conjunto de componentes. Não é obrigatório um contêiner de componente, mas estamos usando um aqui para ajudar no tratamento de eventos.
Por enquanto, o aplicativo usa um arquivo de dados para carregar dados estáticos para testes. Na próxima unidade, você aprenderá a extrair dados dinâmicos de uma organização.
Composição de componentes
Vamos adicionar ao projeto alguns arquivos que podem ser implantados em uma organização.
- Baixe os arquivos para este aplicativo aqui: Aplicativo seletor de bicicletas do Trailhead.
- Descompacte os arquivos na pasta force-app/main/default/lwc do projeto bikeCard.
Relacionamentos do componente
Neste aplicativo, vários componentes funcionam em conjunto; alguns componentes estão aninhados dentro de outros componentes. Da mesma forma que você aninha elementos HTML dentro uns dos outros, os componentes Web do Lightning, que são elementos HTML personalizados, podem ser aninhados dentro de outros componentes Web do Lightning.
Em nosso sistema de arquivos, as pastas dos componentes realmente não dão indícios sobre os relacionamentos entre eles.
Vamos ver como os componentes foram aninhados no nível da interface do usuário em um diagrama.
Ao olhar para os arquivos, você pode ver que o componente selector define a página e torna os componentes da lista (c-list
) e detalhes (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>
Atualize detail.html com o seguinte:
<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>
Olhando detail.html, você pode ver renderização condicional (lwc:if={product}
e lwc:else
). Se não tiver nada escolhido na lista, será exibida uma mensagem solicitando que o usuário escolha algo. Se algo for escolhido, serão exibidas informações sobre a bicicleta.
O componente list processa vários componentes tile(c-tile
), um para cada bicicleta nos dados. Esse aninhamento é alcançado no HTML para cada componente pai. Por exemplo, o componente list tem o seguinte HTML, incluindo o componente tile 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 como cada iteração do item da bicicleta gera um novo componente tile. A simples inclusão da marca do componente c-tile
torna todos os componentes tile em filho. A definição da div class "container" é usada para adicionar estilos para que você possa controlar a organização dos blocos. Se observar list.css, verá que ela encapsula o conteúdo.
.container { display: flex; flex-direction: row; flex-wrap: wrap; }
O relacionamento pai/filho é importante, não só para o design do aplicativo, mas, também, para o tratamento de eventos.
Vamos nos aprofundar um pouco mais no tratamento de eventos.
Eventos para cima, propriedades para baixo
Em um componente complexo (um que contenha vários componentes pai e filho), os componentes podem se comunicar para cima e para baixo.
- O componente filho c-todo-item envia um evento ao componente pai c-todo-app. Por exemplo, o componente filho pode transmitir um objeto de evento para o componente pai quando um usuário clica em um botão para que o pai possa tratar do evento e alterar a página atual.
- O componente pai c-todo-app transmite uma propriedade ou invoca um método no componente filho. Por exemplo, o pai pode definir um valor de texto em um componente filho ou invocar um método no componente filho.
Vamos ver como essa comunicação funciona.
Como transmitir informações para cima
As informações podem ser transmitidas usando eventos e ouvintes de eventos.
O componente filho envia o evento e o componente pai escuta-o. O envio do evento inclui a criação de um objeto de evento que o filho pode transmitir ao componente pai. O pai tem um manipulador para responder ao evento.
Por exemplo (não crie esses componentes), um componente filho como este contém um método nextHandler()
que cria um objeto de evento simples usando CustomEvent()
e envia o tipo de evento 'next' quando o usuário clica em um botão Avançar.
// todoItem.js import { LightningElement } from 'lwc'; ... nextHandler() { this.dispatchEvent(new CustomEvent('next')); } }
O componente pai ouve o evento com o manipulador de eventos inline prefixado com 'on'(onnex).
<!-- todoApp.html --> <template> <c-todo-item onnext={nextHandler}></c-todo-item> </template>
E transmite o objeto do evento a um manipulador de eventos.
// todoApp.js import { LightningElement } from 'lwc'; export default class TodoApp extends LightningElement { ... nextHandler(){ this.page = this.page + 1; } }
Como transmitir informações para baixo
As informações podem ser transmitidas usando propriedades públicas e métodos públicos.
Torne uma propriedade de componente pública precedendo-a com o decorador @api
. Em seguida, defina a propriedade como pública por meio de um componente externo.
Por exemplo (não crie esses componentes), se o componente filho c-todo-item tiver o seguinte:
// todoItem.js import { LightningElement, api } from 'lwc'; export default class TodoItem extends LightningElement { @api itemName; }
Defina o valor do pai com o seguinte:
<!-- todoApp.html --> <template> <c-todo-item item-name="Milk"></c-todo-item> </template>
Observe que a variável itemName
é definida usando o atributo com palavras separadas por hifens item-name
. Os nomes de propriedade no JavaScript usam letras concatenadas, enquanto os nomes de atributos HTML usam palavras separadas por hifens para corresponder aos padrões HTML. O atributo item-name
na marcação mapeia para a propriedade itemName
do JavaScript.
As propriedades públicas são ótimas soluções para transmitir valores primitivos, objetos simples e matrizes.
Além disso, você pode utilizar getters e setters para usar lógica quando as propriedades são get ou set. Lembre-se de anotá-los com o decorador @api
para torná-los públicos para outros componentes.
Da mesma forma, você pode criar métodos públicos que podem ser chamados a partir de um componente pai. Crie um método público no componente filho definindo-o com o decorador @api
e, em seguida, chame-o do componente pai.
Vamos supor que temos um componente filho como este (não crie esses componentes).
// videoPlayer.js import { LightningElement, api } from 'lwc'; export default class VideoPlayer extends LightningElement { @api play() { // Play music! } }
Quando o componente c-video-player é incluído em um componente pai, podemos invocar o método do componente pai da seguinte maneira:
// methodCaller.js import { LightningElement } from 'lwc'; export default class MethodCaller extends LightningElement { handlePlay() { this.template.querySelector('c-video-player').play(); } }
Definimos um método handlePlay()
que aciona o evento. Em seguida, usamos o método DOM querySelector()
para pesquisar um elemento DOM chamado c-video-player e invocar seu método público.
Tratamento de eventos em HTML
Nosso aplicativo seletor precisa tratar de um tipo de evento: o usuário que clica em um bloco. Quando isso acontece, o componente detail deve voltar a realizar a renderização com as informações do bloco relacionado. Você pode tratar de eventos em HTML (adicionar um ouvinte para o evento no modelo) ou JavaScript (escrever uma função de ouvinte para o evento). Recomendamos o uso da abordagem HTML da seguinte maneira.
Cada componente tile ouve o clique do usuário porque o HTML do componente tile (tile.html) tem um ouvinte para o evento 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 um usuário clica em uma das instâncias de blocos na interface do usuário, o ouvinte onclick
chama a função tileClick
do manipulador no arquivo 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); } }
Padrão de eventos do aplicativo seletor
Em nosso aplicativo seletor de produtos, usamos um componente complexo (que contém vários componentes pai e filho). Recomendamos que você propague o evento por toda hierarquia de componentes para que os componentes pai possam responder a eventos filho. Se você tiver outros componentes filho (não o que aciona o evento), como resposta ao evento, poderá transmitir uma propriedade para baixo, a esses filhos.
O padrão tem essa aparência:
Para fazer isso, precisamos encadear os ouvintes e manipuladores de eventos para cima, pela hierarquia, para o componente ebikes. Em seguida, precisamos transmitir uma propriedade para baixo ao componente detail.
Em nossos arquivos, você verá o seguinte.
- tile.html tem o ouvinte de evento
onclick
que chama o manipuladortileClick
.
- tile.js tem o método
tileClick
que cria um novoCustomEvent
com o tipo de eventotileclick
e um objeto contendo um valordetail
(this.product.fields.Id.value
).
- list.html tem o ouvinte
ontileclick
que chama o manipuladorhandleTileClick
.
- list.js tem o método
handleTileClick
que passa no evento (evt
) para criar outroCustomEvent
(productselected
) com um objeto também contendo um valordetail
evt.detail
. E envia o evento em JavaScript:// Fire the event from c-list this.dispatchEvent(event);
- selector.html tem o ouvinte de evento
onproductselected
que chama o manipuladorhandleProductSelected
.
- selector.js tem o método
handleProductSelected
definido comoselectedProductId
para o valorevt.detail
que foi passado para ele. A variável "selectedProductId" é passada do componente selector para o componente detail em selector.html:product-id={selectedProductId}
.
- detail.html tem uma diretiva condicional (você se lembra das diretivas da unidade 2?) que aguarda um valor product:
<template lwc:if={product}>
- detail.js reúne as partes. Ele cria uma variável privada
_productId
para rastrear o estado do valorproductId
. Em seguida, ele usa um padrão get/set para obter o valor e configurá-lo para uma variávelproduct
que permite que detail.html carregue o conteúdo condicional.
Getters e setters são uma construção comum de JavaScript. Eles permitem adicionar lógica e condições a atribuições de propriedade.
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 você clica em um bloco, esse processo se repete.
Implantar seus arquivos na organização
Vamos implantar esses novos arquivos do projeto bikeCard em sua organização para ver como ela funciona. Usando as mesmas etapas executadas na última unidade, implante os novos arquivos, abra a organização e crie uma página no Criador de aplicativo Lightning com esse aplicativo.
- No projeto bikeCard do VS Code, clique com o botão direito do mouse na pasta force-app/main/default e selecione SFDX: Deploy Source to Org (SFDX: Implantar origem na organização).
- Na Paleta de comando no VS Code, use SFDX: Open Default Org (Abrir a organização padrão) para abrir sua organização.
- Crie a página de uma região usando o componente selector.
- Dê-lhe o rótulo
Your Bike Selection
(Sua seleção de bicicletas).
- Arraste seu componente selector para o topo do layout de páginas.
- Salve e ative para todos os usuários.
- Abra-o e veja o funcionamento do componente na interface do usuário.
Você tem uma página totalmente interativa composta por vários componentes que funcionam em conjunto. Em seguida, vamos experimentar o estilo e a obtenção de dados em tempo real de uma organização.
Recursos
- Guia do desenvolvedor de Componentes Web do Lightning: Shadow DOM
- Guia do desenvolvedor de Componentes Web do Lightning: Comunicar com eventos
- Guia do desenvolvedor de Componentes Web do Lightning: Criar getters e setters