Saturday, April 8, 2023

Create and Dispatch Events in LWC

Create and dispatch events in a component’s JavaScript class. To create an event, use the CustomEvent() constructor. To dispatch an event, call the EventTarget.dispatchEvent() method.

The CustomEvent() constructor has one required parameter, which is a string indicating the event type. As a component author, you name the event type when you create the event. You can use any string as your event type. However, we recommend that you conform with the DOM event standard.

  • No uppercase letters
  • No spaces
  • Use underscores to separate words

Don’t prefix your event name with the string on, because inline event handler names must start with the string on. If your event is called onmessage, the markup would be <c-my-component ononmessage={handleMessage}>. Notice the doubled word onon, which is confusing.

Let’s jump into some code.

The c-paginator component contains Previous and Next buttons. When a user clicks the buttons, the component creates and dispatches previous and next events. You can drop the paginator component into any component that needs Previous and Next buttons. That parent component listens for the events and handles them.

<!-- paginator.html -->
<template>
    <lightning-layout>
        <lightning-layout-item>
            <lightning-button label="Previous" icon-name="utility:chevronleft"  
onclick={previousHandler}></lightning-button>
        </lightning-layout-item>
        <lightning-layout-item flexibility="grow"></lightning-layout-item>
        <lightning-layout-item>
            <lightning-button label="Next" icon-name="utility:chevronright" 
icon-position="right" onclick={nextHandler}></lightning-button>
        </lightning-layout-item>
    </lightning-layout>
</template>

When a user clicks a button, the previousHandler or nextHandler function executes. These functions create and dispatch the previous and next events.

// paginator.js
import { LightningElement } from 'lwc';

export default class Paginator extends LightningElement {
    previousHandler() {
        this.dispatchEvent(new CustomEvent('previous'));
    }

    nextHandler() {
        this.dispatchEvent(new CustomEvent('next'));
    }
}

These events are simple “something happened” events. They don’t pass a data payload up the DOM tree, they simply announce that a user clicked a button.

Let’s drop paginator into a component called c-event-simple, which listens for and handles the previous and next events.

To listen for events, use an HTML attribute with the syntax oneventtype. Since our event types are previous and next, the listeners are onprevious and onnext.

<!-- eventSimple.html -->
<template>
    <lightning-card title="EventSimple" icon-name="custom:custom9">
        <div class="slds-m-around_medium">
            <p class="slds-m-vertical_medium content">Page {page}</p>
            <c-paginator onprevious={previousHandler} onnext={nextHandler}></c-paginator>
        </div>
    </lightning-card>
</template>

When c-event-simple receives the previous and next events, previousHandler and nextHandler increase and decrease the page number.

// eventSimple.js
import { LightningElement } from 'lwc';

export default class EventSimple extends LightningElement {
    page = 1;

    previousHandler() {
        if (this.page > 1) {
            this.page = this.page - 1;
        }
    }

    nextHandler() {
        this.page = this.page + 1;
    }
}

Example Check out the c-event-simple component and the c-paginator component in the github.com/trailheadapps/lwc-recipes repo.

Pass Data in an Event

To pass data up to a receiving component, set a detail property in the CustomEvent constructor. Receiving components access the data in the detail property in the event listener’s handler function.


Warning The CustomEvent interface imposes no type requirements or structure on the detail property. However it’s important to send only primitive data. JavaScript passes all data types by reference except for primitives. If a component includes an object in its detail property, any listener can mutate that object without the component’s knowledge. This is a bad thing! It’s a best practice either to send only primitives, or to copy data to a new object before adding it to the detail property. Copying the data to a new object ensures that you’re sending only the data you want, and that the receiver can’t mutate your data.

Let’s look at the c-event-with-data component from the lwc-recipes repo.

List of contacts with a selected contact that shows name, title, phone number, and email address.

Each item in the list of contacts is a nested c-contact-list-item component.

The c-contact-list-item component wraps the contact name and picture in an anchor tag with an onclick event listener that executes the selectHandler function.

<!-- contactListItem.html -->
<template>
    <a href="#" onclick={selectHandler}>
        <lightning-layout vertical-align="center">
            <lightning-layout-item>
                <img src={contact.Picture__c}></img>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small">
                <p>{contact.Name}</p>
            </lightning-layout-item>
        </lightning-layout>
    </a>
</template>

When a user clicks to select a contact, the component creates and dispatches a CustomEvent called selected. The event includes the data detail: this.contact.Id, which is the ID of the selected contact. The parent, c-event-custom, uses the contact reference to display more information about the contact.

// contactListItem.js
import { LightningElement, api } from 'lwc';

export default class ContactListItem extends LightningElement {
    @api contact;

    selectHandler(event) {
        // Prevents the anchor element from navigating to a URL.
        event.preventDefault();

        // Creates the event with the contact ID data.
        const selectedEvent = new CustomEvent('selected', { detail: this.contact.Id });

        // Dispatches the event.
        this.dispatchEvent(selectedEvent);
    }
}

The c-event-with-data component listens for the selected event in the onselected attribute and handles it in the contactSelected event handler.

<!-- eventWithData.html -->
<template>
    <lightning-card title="EventWithData" icon-name="custom:custom9">
        <lightning-layout class="slds-m-around_medium">
            <lightning-layout-item>
                <template lwc:if={listIsNotEmpty}>
                    <template for:each={contacts.data} for:item="contact">
                        <c-contact-list-item key={contact.Id} contact={contact} 
                           onselected={contactSelected}></c-contact-list-item>
                    </template>
                </template>
            </lightning-layout-item>
            <lightning-layout-item class="slds-m-left_medium">
                <template lwc:if={selectedContact}>
                    <img src={selectedContact.Picture__c}></img>
                    <p>{selectedContact.Name}</p>
                    <p>{selectedContact.Title}</p>
                    <p><lightning-formatted-phone value={selectedContact.Phone}>
                       </lightning-formatted-phone></p>
                    <p><lightning-formatted-email value={selectedContact.Email}>
                        </lightning-formatted-email></p>
                </template>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

In the contactSelected event handler, the component assigns the event.detail property to the contactId property. It finds the contact with that ID in the contacts array provisioned via @wire, and displays the contact’s name, title, phone, and email in the template.

// eventWithData.js
import { LightningElement, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';

export default class EventWithData extends LightningElement {
    selectedContact;

    @wire(getContactList) contacts;

    contactSelected(event) {
        const contactId = event.detail;
        this.selectedContact = this.contacts.data.find(contact => contact.Id === contactId);
    }

    get listIsNotEmpty() {
        return this.contacts && Array.isArray(this.contacts.data) 
                   && this.contacts.data.length > 0;
    }
}

Example Find this sample code in the github.com/trailheadapps/lwc-recipes repo.

No comments:

Post a Comment

Understanding Wire vs Imperative Apex Method Calls in Salesforce Lightning Web Components (LWC)

Understanding Wire vs Imperative Apex Method Calls in Salesforce Lightning Web Components (LWC) Introduction: Salesforce Lightning Web ...