How To Register, Fire And Handle A Component And Application Event
Learning Objectives
After completing this unit, you'll be able to:
- Define custom events for your apps.
- Create and fire events from a component controller.
- Create action handlers to catch and handle events that other components send.
- Refactor a large component into smaller components.
Connect Components with Events
In this unit we're going to tackle the last slice of unfinished functionality in our piffling expenses app: the Reimbursed? checkbox. You're probably thinking that implementing a checkbox would be a short topic. We could certainly take some shortcuts, and brand information technology a very brusk topic.
But this unit, in addition to making the checkbox work, is virtually removing all of the shortcuts we've already taken. We're going to spend this unit of measurement "Doing It Right." Which, in a few places, ways refactoring work we did earlier.
Earlier nosotros start on that, let'south first talk near the shortcuts we took, the Right Style, and why the Right Fashion is (a little bit) harder, but likewise better.
Limerick and Decomposition
If you take a look at our little expenses app in source code form, and list the separate code artifacts, you lot'll come up with something like the following.
-
expenses component
-
expenses.cmp
-
expensesController.js
-
expensesHelper.js
-
-
expensesList component
-
expensesList.cmp
-
-
expenseItem component
-
expenseItem.cmp
-
-
ExpensesController (server-side)
-
ExpensesController.apex
-
Here's how everything comes together, with the createExpense
and updateExpense
events you're wiring up after.
Merely, if you look at the app on screen, what do you see? What y'all should see, and what you lot'll eventually see everywhere you look, is that the app breaks down into many more components. You'll see that you tin decompose our app further, into smaller pieces, than we've done and then far. At the very to the lowest degree, nosotros hope you see that the Add Expense course really should be its own, separate component. (That's why we drew a box around it in the user interface!)
Why didn't nosotros make that form a separate component? Not doing that is by far the biggest shortcut we took over the class of this module. It's worse than the hack we called "icky," in terms of software blueprint. The correct way to build a Lightning Components app is to create independent components, and then compose them together to build new, higher level features. Why didn't nosotros have that approach?
Nosotros took the shortcut, nosotros kept the Add together Expense form within the main expenses
component, because it kept the main expenses
array component aspect and the controller code that affected information technology in the same component. Nosotros wanted the createExpense()
helper role to be able to bear on the expenses
array directly. If we'd moved the Add Expense form into a dissever component, that wouldn't accept been possible.
Why not? We covered the reason briefly very early on, but we desire to really hammer on it now. Lightning components are supposed to be self-contained. They are stand-solitary elements that encapsulate all of their essential functionality. A component is non allowed to reach into another component, even a child component, and modify its internals.
In that location are two primary ways to collaborate with or affect another component. The first mode is i we've seen and done quite a fleck of already: setting attributes on the component'due south tag. A component's public attributes constitute one part of its API.
The second mode to interact with a component is through events. Like attributes, components declare the events they transport out, and the events they can handle. Like attributes, these public events establish a part of the component's public API. We've actually used and handled events already, merely the events have been hiding behind some convenience features. In this unit, nosotros'll drag events out into the lite, and create a few of our own.
These two mechanisms—attributes and events—are the API "sockets," the means you lot connect components together to class complete circuits. Events are as well, behind the scenes, the electrons flowing through that excursion. Merely that's only 1 way that events are different from attributes.
When you set the onclick
aspect on a <lightning:button>
to an action handler in a component'southward controller, you create a directly relationship between those two components. They are linked, and while they're using public APIs to remain contained of each other, they're still coupled.
Events are unlike. Components don't send events to another component. That'south non how events work. Components circulate events of a particular type. If in that location'south a component that responds to that type of outcome, and if that component "hears" your event, then it will act on information technology.
You can call back of the divergence between attributes and events equally the difference between wired circuits and wireless circuits. And we're not talking wireless phones here. 1 component doesn't get the "number" for another component and telephone call it up. That would exist an aspect. No, events are like wireless broadcasts. Your component gets on the radio, and sends out a message. Is there anyone out there with their radio prepare turned on, and tuned to the right frequency? Your component has no way of knowing—so you should write your components in such a style that it's OK if no one hears the events they broadcast. (That is, things might not work, only nada should crash.)
Sending an Event from a Component
OK, enough theory, let'south do something specific with our app, and meet how events work in code. We will start by implementing the Reimbursed? checkbox. Then we'll take what we learned doing that, and utilize it to refactor the Add together Expense form into its own component, the way the Great Engineer intended.
Offset, let'due south focus on the click handler on the <lightning:input>
for the Reimbursed__c
field.
<lightning:input type="toggle" characterization="Reimbursed?" proper name="reimbursed" grade="slds-p-around_small" checked="{!5.expense.Reimbursed__c}" messageToggleActive="Aye" messageToggleInactive="No" onchange="{!c.clickReimbursed}"/>
Before we dive into the click handler, permit's take a stride back to catch a glimpse on what <lightning:input>
has to offer. type="toggle"
is really a checkbox with a toggle switch blueprint. class
enables you lot to apply custom CSS styling or employ SLDS utilities. messageToggleActive
and messageToggleInactive
provide custom labels for the checked and unchecked positions. These handy attributes are just several of many others on <lightning:input>
. Finally, the onchange
attribute of <lightning:input>
gives us an easy way to wire the toggle switch to an action handler that updates the record when y'all slide right (checked) or slide left (unchecked).
Now, let's think near what should happen when it's checked or unchecked. Based on the code we wrote to create a new expense, updating an expense is probably something similar this.
- Get the expense particular that inverse.
- Create a server activeness to update the underlying expense record.
- Packet expense into the activeness.
- Gear up a callback to handle the response.
- Fire the action, sending the request to the server.
- When the response comes and the callback runs, update the
expenses
attribute.
Um, what expenses
attribute? Look at our component markup once again. No expenses
, but a atypical expense
. Hmmm, correct, this component is just for a single item. There'due south an expenses
attribute on the expensesList
component…simply that'due south not even the "existent" expenses
. The real one is a component attribute in the top-level expenses
component. Hmmm.
Is there a component.get("v.parent")
? Or would it accept to exist component.become("5.parent").get("v.parent")
—something
that would let us get a reference to our parent's parent, so nosotros can set expenses
there?
Stop. Right. At that place.
Components exercise not accomplish into other components and set values on them. There's no way to say "Hey grandparent, I'm gonna update expenses
." Components proceed their hands to themselves. When a component wants an antecedent component to make a modify to something, it asks. Nicely. By sending an event.
Here's the absurd part. Sending an event looks well-nigh the same as handling the update direct. Here's the lawmaking for the clickReimbursed
handler.
({ clickReimbursed: office(component, effect, helper) { let expense = component.become("v.expense"); permit updateEvent = component.getEvent("updateExpense"); updateEvent.setParams({ "expense": expense }); updateEvent.fire(); } })
Whoa. That's pretty simple! And it does wait kind of like what we envisioned higher up. The preceding lawmaking for clickReimbursed
does the following:
- Gets the expense that changed.
- Creates an upshot named updateExpense.
- Packages expense into the result.
- Fires the event.
The callback stuff is missing, but otherwise this is familiar. Simply…what'due south going to handle calling the server, and the server response, and update the chief expenses
array attribute? And how exercise we know almost this updateExpense
event, anyway?
updateExpense
is a custom event, that is, an effect we write ourselves. Yous can tell because, unlike getting a server action, we use component.getEvent()
instead of component.get()
. Also, what we are getting doesn't take a value provider, just a name. Nosotros'll define this event in simply a moment.
Equally for what's going to handle calling the server and handling the response, let's talk about information technology. We could implement the server request and handle the response right here in the expenseItem
component. Then we'd send an effect to just rerender things that depend on the expenses
array. That would be a perfectly valid design choice, and would go along the expenseItem
component totally self-independent, which is desirable.
However, as nosotros'll come across, the lawmaking for creating a new expense and the lawmaking for updating an existing expense are very like, enough so that we'd prefer to avoid duplicate code. So, the design selection we've made is to ship an updateExpense
event, which the main expenses
component will handle. Later, when we refactor our grade, we'll do the same for creating a new expense.
By having all child components delegate responsibleness for handling server requests and for managing the expenses
array attribute, we're breaking encapsulation a bit. Just, if you call up of these child components equally the internal implementation details of the expenses
component, that's OK. The main expenses
component is self-contained.
You lot have a option: consolidation of disquisitional logic, or encapsulation. You'll brand merchandise-offs in Aureola components just like you make trade-offs in any software design. Only make certain you lot document the details.
Defining an Result
The first thing we'll do is define our custom event. In the Developer Console, select File | New | Lightning Effect , and proper name the event "expensesItemUpdate". Replace the default contents with the post-obit markup.
<aura:event type="COMPONENT"> <aureola:attribute proper noun="expense" type="Expense__c"/> </aura:upshot>
There are two types of events, component and application. Here nosotros're using a component event, because we desire an antecedent component to catch and handle the event. An ancestor is a component "above" this one in the component hierarchy. If we wanted a "full general broadcast" kind of effect, where any component could receive information technology, we'd use an application outcome instead.
The full differences and correct usage of application vs. component events isn't something nosotros're able to get into here. Information technology's a more advanced topic, and there are enough complicated details that information technology's a distraction from our purpose in this module. When you lot're set up for more, the Resources volition assist you out.
The other affair to notice most the issue is how compact the definition is. We named the issue when it was created, expensesItemUpdate
, and its markup is a outset and ending <aura:effect>
tag, and one <aura:attribute>
tag. An issue's attributes describe the payload information technology can carry. In the clickReimbursed
action handler, we prepare the payload with a call to setParams()
. Hither in the event definition, we see how the event parameter is defined, and that there are no other valid parameters.
And that's pretty much all there is to defining events. You don't add implementation or beliefs details to events themselves. They're just packages. In fact, some events don't have any parameters at all. They're only messages. "This happened!" All of the behavior virtually what to do if "this" happens is divers in the components that send and receive the consequence.
Sending an Event
Nosotros already looked at how to actually fire an upshot, in the clickReimbursed
action handler. But for that to work, nosotros demand to practise one concluding thing, and that's register the event. Add together this line of markup to the expenseItem
component, right below its attribute definitions.
<aura:registerEvent proper noun="updateExpense" type="c:expensesItemUpdate"/>
This markup says that our component fires an upshot, named "updateExpense", of type "c:expensesItemUpdate". But, wasn't "expensesItemUpdate" the name of the event when we defined it? And what happened to component or application event types?
You're right to think it'due south a niggling disruptive—it really is a bit of a switch-a-roo. It might help to think of "application" and "component" as Aura components framework effect types, while the types that come from the names of events y'all define are custom event types, or outcome structure types. That is, when yous define an event, y'all define a packet format. When yous register to send an event, you declare what format it uses.
The process of defining and registering an effect might still seem a chip weird, so permit'southward look ahead a bit. Here in expenseItem
, we're going to send an effect named updateExpense
. Later on in expenseForm
, we're going to ship an effect named createExpense
. Both of these events need to include an expense to be saved to the server. And so they both use the c:expensesItemUpdate
issue type, or bundle format, to ship their events.
On the receiving end, our main expenses
component is going to annals to handle both of these events. Although the server call ends up being the same, the user interface updates are slightly different. Then how does expenses
know whether to create or update the expense in the c:expensesItemUpdate
package? By the proper noun of the event being sent.
Understanding the distinction hither, and how i event can be used for multiple purposes, is a calorie-free seedling moment in learning Lightning Components. If y'all haven't had that moment quite nonetheless, yous'll accept it when you await at the rest of the code.
Before we move on to handling events, let's summarize what it takes to send them.
- Define a custom consequence by creating a Lightning Event, giving it a name and attributes.
- Annals your component to send these events, past choosing a custom event type and giving this specific use of that type a proper name.
- Fire the event in your controller (or helper) code by:
- Using
component.getEvent()
to create a specific event instance. - Sending the outcome with
fire()
.
- Using
If you went ahead and implemented all of the code we just looked at, you lot tin test it out. Reload your app, and toggle a Reimbursed? checkbox a few times. If you missed a stride, you'll become an error, and you should recheck your work. If yous did everything correct…hey, wait, the expense changes colour to show its Reimbursed? status, just equally expected!
This behavior was present before we even started this unit. That's the effect of the <lightning:input>
component having value="{!v.expense.Reimbursed__c}"
set. When you lot toggle the switch, the local version of the expense
is updated. Just that change isn't being sent up to the server. If you look at the expense record in Salesforce, or reload the app, you won't see the change.
Why non? Nosotros've just done half of the work to create a complete excursion for our issue. Nosotros have to cease wiring the circuit by creating the event handler on the other side. That effect handler will take care of sending the modify to the server, and making the update durable.
Handling an Event
Enabling the expenseItem
component to ship an event required three steps. Enabling the expenses
component to receive and handle these events requires three parallel steps.
- Define a custom event. We've already done this, because expenseItem is sending the same custom result that expenses is receiving.
- Register the component to handle the event. This maps the event to an activity handler.
- Actually handle the issue in an action handler.
Since we've already done pace i, let's immediately turn to stride 2, and register expenses to receive and handle the updateExpense result. Like registering to send an event, registering to handle i is a single line of markup, which yous should add to the expenses
component right after the init
handler.
<aura:handler name="updateExpense" event="c:expensesItemUpdate" action="{!c.handleUpdateExpense}"/>
Like the init
handler, this uses the <aura:handler>
tag, and has an activeness
attribute that sets the controller action handler for the result. Like when yous registered the result in expenseItem
, hither you ready the name and type of the event—though note the use of the much more than sensibly named upshot
attribute for the blazon.
In other words, there'due south not much hither you haven't seen earlier. What's new, and specific to handling custom events, is the combination of the attributes, and knowing how this receiver "socket" in expenses
matches up with the sender "socket" in expenseItem
.
This completes the wiring office of making this work. All that's left is to actually write the action handler!
We'll commencement with the handleUpdateExpense
activeness handler. Here'due south the code, and exist sure to put it riiiiiight under the clickCreate
action handler.
handleUpdateExpense: function(component, issue, helper) { let updatedExp = event.getParam("expense"); helper.updateExpense(component, updatedExp); }
Huh. That's interesting. Except for the form validation check and the specific helper function we're delegating the work to, it looks like this action handler is the same as handleCreateExpense
.
And at present permit'southward add the updateExpense
helper role. As nosotros did with the activeness handler, make sure you put this code right below the createExpense
helper part.
updateExpense: function(component, expense) { permit action = component.get("c.saveExpense"); action.setParams({ "expense": expense }); action.setCallback(this, function(response){ let state = response.getState(); if (country === "SUCCESS") { // do nothing! } }); $A.enqueueAction(action); },
Two things you should notice right off the bat. Get-go, except for the callback specifics, the updateExpense
helper method is identical to the createExpense
helper method. That smells similar opportunity.
2nd, about those callback specifics. What gives? How tin the right thing to do be nothing?
Think about it for a moment. Earlier, when testing sending the event (if non before), we saw that the expenseItem
component's color inverse in response to toggling the Reimbursed? checkbox. Recall the explanation? The local re-create of the expense tape is already updated! And so, at least for the moment, when the server tells us it was successful at updating its version, we don't have to do anything.
Notation that this code but handles the case where the server is successful at updating the expense record. We'd definitely have some piece of work to practise if at that place was an error. Say, Bookkeeping flagged this expense every bit not-reimbursable, making it impossible to set this field to true
. But that, every bit they say, is a lesson for another day.
Refactor the Helper Functions
Let's become back to that opportunity nosotros saw to factor out some common lawmaking. The ii helper functions are identical except for the callback. And then, permit'south make a new, more generalized function that takes the callback
as a parameter.
saveExpense: function(component, expense, callback) { permit activity = component.go("c.saveExpense"); activity.setParams({ "expense": expense }); if (callback) { action.setCallback(this, callback); } $A.enqueueAction(activeness); },
The callback
parameter is optional. If it'due south in that location, we'll pass it along to action
. Unproblematic. And at present we can reduce our event-specific helper functions to the post-obit code.
createExpense: role(component, expense) { this.saveExpense(component, expense, function(response){ permit state = response.getState(); if (land === "SUCCESS") { let expenses = component.become("v.expenses"); expenses.push button(response.getReturnValue()); component.set("five.expenses", expenses); } }); }, updateExpense: function(component, expense) { this.saveExpense(component, expense); },
createExpense
is only a lilliputian shorter, but it's exclusively focused on what to do when the response comes back (the callback). And wow, updateExpense
is a ane-liner!
Refactor the Add Expense Form
That little refactoring practise was so satisfying, and using events was and then (we're sorry) electrifying, let's practice information technology once more, but bigger. More cowbell!
This next task involves extracting the Add Expense form from the expenses
component, and moving information technology to its ain, new component. Extracting the class markup is easy enough, a uncomplicated copy-and-paste exercise. Just what else moves with it? Before nosotros first moving pieces around willy-nilly, let's think nigh what moves and what stays.
In the current pattern, the form's action handler, clickCreate
, handles input validation, sending the request to the server, and updating local land and user interface elements. The course will all the same demand an action handler, and should probably still handle form validation. But we'll programme on having the residual stay backside, considering we're keeping our server request logic consolidated in the expenses
component.
So there'due south a little (merely just a fiddling!) teasing apart to do there. Our programme, and so, is to start by moving the form markup, and so move every bit piddling every bit possible after that to make it work correctly. We'll refactor both components to communicate via events, instead of via direct access to the expenses
assortment component attribute.
Permit'due south go started!
In the main expenses
component, select everything between the two <!-- CREATE NEW EXPENSE -->
comments, including the kickoff and ending comments themselves. Cut information technology to your clipboard. (Aye, cut. We're committed.)
Create a new Aura component, and name it "expenseForm". Paste the copied Add Expense class markup into the new component, between the <aura:component>
tags.
Back to the expenses
component. Add the new expenseForm
component to the markup. That section of expenses
should look like this.
<!-- NEW EXPENSE FORM --> <lightning:layout > <lightning:layoutItem padding="effectually-small" size="6"> <c:expenseForm/> </lightning:layoutItem> </lightning:layout>
At this signal, you can reload your app to see the changes. There should be no visible changes. But, unsurprisingly, the Create Expense push no longer works.
Permit'south get apace through the rest of the moving things around part.
Next, move the newExpense
attribute from the expenses
component to the expenseForm
component markup. This is used for the course fields, so it needs to be in the form component. Information technology moves over with no changes required, so simply cut from one and paste in the other.
In the expenseForm
component, create the controller and helper resource.
Move the clickCreate
action handler from the expenses
controller to the expenseForm
controller. The button is in the form component, and so the action handler for the button needs to be there, too. Believe it or not, this also needs no changes. (You might begin sensing a theme here.)
At present we need to make a couple of bodily changes. But these will exist familiar, because we're just calculation consequence sending, which we did earlier for expenseItem
. And expenseItem
, you'll recall, also sends an event with an expense
payload, which is handled by the expenses
component.
In the expenseForm
helper, create the createExpense
function.
createExpense: function(component, newExpense) { let createEvent = component.getEvent("createExpense"); createEvent.setParams({ "expense": newExpense }); createEvent.burn down(); },
This looks very much like the clickReimbursed
activeness handler in expenseItem
.
If a component is going to send an effect, it needs to register the event. Add the following to the expenseForm
component markup, just beneath the newExpense
attribute.
<aura:registerEvent proper name="createExpense" type="c:expensesItemUpdate"/>
At this signal, we've done all the work to implement the expenseForm
component. Y'all should be able to reload the app, and the form now "works" in the sense that there are no errors, and you should run across the appropriate form messages when you enter invalid data. If yous're using the Salesforce Lightning Inspector, y'all can even meet that the expensesItemUpdate
result is being fired. All that'due south left is to handle information technology.
Before we handle the consequence, please do notice how easy this refactoring was. About of the code didn't change. There's a total of six lines of new code and markup in the preceding steps. Information technology'southward unfamiliar to practise this work today, but do information technology a few times, and you realize that you lot're just moving a flake of lawmaking around.
OK, let's finish this. The expenseForm
fires the createExpense
event, but we also demand the expenses
component to catch it. First nosotros register the createExpense
event handler, and wire it to the handleCreateExpense
activity handler. Once once more, this is a single line of markup. Add this line correct in a higher place or beneath the updateExpense
event handler.
<aura:handler name="createExpense" issue="c:expensesItemUpdate" activity="{!c.handleCreateExpense}"/>
Finally, for the concluding footstep, create the handleCreateExpense
action handler in the expenses
controller. Add this lawmaking right above or below the handleUpdateExpense
action handler.
handleCreateExpense: function(component, issue, helper) { let newExpense = result.getParam("expense"); helper.createExpense(component, newExpense); },
Yep, that simple. All of the work is delegated to the createExpense
helper role, and that didn't move or change. Our handleCreateExpense
activeness handler is simply there to wire the right things together.
And with that, we're finished showing how to loosely couple components using events. Create and fire the event in one component, catch and handle information technology in some other. Wireless circuits!
Bonus Lesson—Minor Visual Improvements
Before we head off into the dusk, or rather, the challenge, here is a modest visual improvement.
We'd like to amend the layout of our app a fiddling scrap, by adding a few container components. This concluding bit also gives y'all an opportunity to meet the full expense
component after all our changes. In the expense
component, replace the expensesList
markup with the post-obit.
<lightning:layout> <lightning:layoutItem padding="around-small" size="6"> <c:expensesList expenses="{!v.expenses}"/> </lightning:layoutItem> <lightning:layoutItem padding="around-small" size="6"> Put something cool here </lightning:layoutItem> </lightning:layout>
The furnishings of the changes are to add some margins and padding, and make the expenses list more narrow. The layout leaves room to put something over in that location on the right. In the next unit of measurement, nosotros'll suggest a couple of exercises you lot could do on your own.
How To Register, Fire And Handle A Component And Application Event,
Source: https://trailhead.salesforce.com/en/content/learn/modules/lex_dev_lc_basics/lex_dev_lc_basics_events
Posted by: penaseemase.blogspot.com
0 Response to "How To Register, Fire And Handle A Component And Application Event"
Post a Comment