Javascript required
Skip to content Skip to sidebar Skip to footer

Register Click Events Again After Markup Added

Learning Objectives

Later completing this unit, y'all'll exist 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 transport.
  • Refactor a big component into smaller components.

Connect Components with Events

In this unit we're going to tackle the final piece of unfinished functionality in our piddling expenses app: the Reimbursed? checkbox. Yous're probably thinking that implementing a checkbox would be a short topic. We could certainly take some shortcuts, and make it a very short topic.

Simply this unit of measurement, in improver to making the checkbox work, is most removing all of the shortcuts we've already taken. We're going to spend this unit "Doing It Right." Which, in a few places, means refactoring piece of work nosotros did earlier.

Before we showtime on that, let's start talk about the shortcuts we took, the Right Way, and why the Correct Way is (a little bit) harder, but also improve.

Limerick and Decomposition

If yous have a look at our little expenses app in source lawmaking form, and list the split up code artifacts, you'll come upwards with something like the post-obit.

  • expenses component
    • expenses.cmp
    • expensesController.js
    • expensesHelper.js
  • expensesList component
    • expensesList.cmp
  • expenseItem component
    • expenseItem.cmp
  • ExpensesController (server-side)
    • ExpensesController.noon

Hither'due south how everything comes together, with the createExpense and updateExpense events you're wiring up later.

The expenses app is made up of many smaller components.

But, if you await at the app on screen, what do you encounter? What you should see, and what yous'll eventually encounter everywhere y'all look, is that the app breaks downwardly into many more components. Y'all'll see that yous can decompose our app further, into smaller pieces, than we've done and so far. At the very least, we hope yous see that the Add together Expense grade really should exist its own, separate component. (That's why we drew a box effectually it in the user interface!)

Why didn't we brand that form a separate component? Non doing that is by far the biggest shortcut we took over the course of this module. It's worse than the hack we called "disgusting," in terms of software design. The right mode to build a Lightning Components app is to create independent components, then compose them together to build new, higher level features. Why didn't we take that approach?

We took the shortcut, we kept the Add together Expense form within the primary expenses component, because it kept the main expenses array component attribute and the controller code that affected it in the same component. Nosotros wanted the createExpense() helper function to be able to touch the expenses array directly. If we'd moved the Add Expense course into a separate component, that wouldn't have been possible.

Why non? We covered the reason briefly very early, but nosotros want to really hammer on it now. Lightning components are supposed to be self-independent. They are stand-alone elements that encapsulate all of their essential functionality. A component is not allowed to achieve into another component, fifty-fifty a child component, and alter its internals.

There are 2 principal ways to collaborate with or bear on some other component. The first way is one we've seen and done quite a bit of already: setting attributes on the component'due south tag. A component's public attributes constitute one part of its API.

The second way to interact with a component is through events. Like attributes, components declare the events they send out, and the events they tin handle. Like attributes, these public events constitute a part of the component'southward public API. Nosotros've actually used and handled events already, just the events have been hiding backside some convenience features. In this unit, we'll drag events out into the light, and create a few of our own.

These two mechanisms—attributes and events—are the API "sockets," the means y'all connect components together to form complete circuits. Events are also, backside the scenes, the electrons flowing through that excursion. But that'southward just i style that events are unlike from attributes.

When you set the onclick attribute on a <lightning:button> to an activity handler in a component's controller, you lot create a direct relationship between those two components. They are linked, and while they're using public APIs to remain contained of each other, they're nevertheless coupled.

Events are different. Components don't send events to another component. That's not how events piece of work. Components broadcast events of a particular type. If there's a component that responds to that type of upshot, and if that component "hears" your event, then information technology will act on information technology.

You can remember of the divergence between attributes and events as the difference between wired circuits and wireless circuits. And nosotros're non talking wireless phones here. 1 component doesn't get the "number" for another component and call it up. That would be an attribute. No, events are like wireless broadcasts. Your component gets on the radio, and sends out a message. Is at that place anyone out there with their radio fix turned on, and tuned to the correct frequency? Your component has no way of knowing—and so you lot should write your components in such a way that it'southward OK if no one hears the events they broadcast. (That is, things might not work, but nil should crash.)

Sending an Result from a Component

OK, enough theory, let'southward practise something specific with our app, and see how events work in code. Nosotros volition first past implementing the Reimbursed? checkbox. Then we'll accept what we learned doing that, and use it to refactor the Add together Expense form into its own component, the way the Bully Engineer intended.

First, permit's focus on the click handler on the <lightning:input> for the Reimbursed__c field.

<lightning:input type="toggle"             label="Reimbursed?"             name="reimbursed"             form="slds-p-around_small"             checked="{!v.expense.Reimbursed__c}"             messageToggleActive="Yes"             messageToggleInactive="No"             onchange="{!c.clickReimbursed}"/>

Before nosotros dive into the click handler, permit's have a stride back to take hold of a glimpse on what <lightning:input> has to offering. type="toggle" is really a checkbox with a toggle switch design. form enables you to use custom CSS styling or use SLDS utilities. messageToggleActive and messageToggleInactive provide custom labels for the checked and unchecked positions. These handy attributes are but several of many others on <lightning:input>. Finally, the onchange attribute of <lightning:input> gives u.s.a. an like shooting fish in a barrel way to wire the toggle switch to an action handler that updates the record when you slide right (checked) or slide left (unchecked).

At present, let's think about what should happen when it'due south checked or unchecked. Based on the code nosotros wrote to create a new expense, updating an expense is probably something like this.

  1. Get the expense item that changed.
  2. Create a server action to update the underlying expense record.
  3. Package expense into the activeness.
  4. Set a callback to handle the response.
  5. Burn the action, sending the request to the server.
  6. When the response comes and the callback runs, update the expenses aspect.

Um, what expenses attribute? Wait at our component markup once more. No expenses, only a singular expense. Hmmm, right, this component is just for a single item. In that location'south an expenses aspect on the expensesList component…only that's not fifty-fifty the "real" expenses. The real i is a component attribute in the top-level expenses component. Hmmm.

Is at that place a component.go("v.parent")? Or would it have to be component.get("v.parent").get("v.parent") —something that would let us get a reference to our parent's parent, so we tin fix expenses there?

Stop. Correct. There.

Components practice non reach into other components and fix values on them. There's no manner to say "Hey grandparent, I'1000 gonna update expenses." Components keep their hands to themselves. When a component wants an ancestor component to make a modify to something, it asks. Nicely. By sending an consequence.

Here's the cool part. Sending an upshot looks almost the same as handling the update directly. Hither'southward the code for the clickReimbursed handler.

({     clickReimbursed: function(component, event, helper) {         permit expense = component.get("v.expense");         let updateEvent = component.getEvent("updateExpense");         updateEvent.setParams({ "expense": expense });         updateEvent.fire();     } })

Whoa. That's pretty elementary! And information technology does look kind of like what we envisioned above. The preceding code for clickReimbursed does the post-obit:

  1. Gets the expense that changed.
  2. Creates an event named updateExpense.
  3. Packages expense into the outcome.
  4. Fires the issue.

The callback stuff is missing, just otherwise this is familiar. Simply…what'due south going to handle calling the server, and the server response, and update the chief expenses assortment attribute? And how practice we know about this updateExpense result, anyhow?

updateExpense is a custom event, that is, an event we write ourselves. You can tell because, unlike getting a server action, we use component.getEvent() instead of component.get(). Too, what we are getting doesn't have a value provider, only a name. We'll define this event in just a moment.

Equally for what's going to handle calling the server and handling the response, permit'southward talk about it. We could implement the server request and handle the response right hither in the expenseItem component. So we'd transport an result to just rerender things that depend on the expenses array. That would be a perfectly valid pattern pick, and would go on the expenseItem component totally self-contained, which is desirable.

Nonetheless, as we'll see, the code for creating a new expense and the lawmaking for updating an existing expense are very similar, enough then that we'd prefer to avoid duplicate code. And so, the blueprint choice nosotros've made is to transport an updateExpense event, which the chief expenses component will handle. Later, when we refactor our form, we'll exercise the same for creating a new expense.

By having all child components consul responsibility for handling server requests and for managing the expenses array aspect, we're breaking encapsulation a bit. But, if you lot think of these child components as the internal implementation details of the expenses component, that's OK. The main expenses component is self-contained.

You accept a selection: consolidation of critical logic, or encapsulation. You lot'll brand merchandise-offs in Aureola components merely like y'all make merchandise-offs in any software design. Just make sure y'all document the details.

Defining an Consequence

The first matter nosotros'll do is define our custom outcome. In the Programmer Console, select File | New | Lightning Consequence , and name the event "expensesItemUpdate". Replace the default contents with the following markup.

<aura:event type="COMPONENT">     <aureola:attribute name="expense" type="Expense__c"/> </aureola:effect>

There are two types of events, component and awarding. Here we're using a component event, because we want an ancestor component to take hold of and handle the outcome. An ancestor is a component "above" this 1 in the component hierarchy. If nosotros wanted a "general broadcast" kind of issue, where whatever component could receive it, we'd employ an awarding event instead.

The full differences and correct usage of awarding vs. component events isn't something we're able to go into here. It's a more advanced topic, and there are plenty complicated details that it's a distraction from our purpose in this module. When you're set for more, the Resources volition help y'all out.

The other thing to notice virtually the result is how meaty the definition is. We named the outcome when information technology was created, expensesItemUpdate, and its markup is a beginning and catastrophe <aureola:result> tag, and ane <aura:attribute> tag. An issue's attributes describe the payload it can bear. In the clickReimbursed action handler, we set up the payload with a call to setParams(). Hither in the event definition, nosotros see how the event parameter is defined, and that there are no other valid parameters.

And that'south pretty much all there is to defining events. Yous don't add implementation or behavior details to events themselves. They're only packages. In fact, some events don't have any parameters at all. They're just messages. "This happened!" All of the behavior virtually what to practise if "this" happens is defined in the components that send and receive the consequence.

Sending an Event

We already looked at how to really fire an event, in the clickReimbursed activity handler. But for that to work, nosotros need to do one last thing, and that'due south register the event. Add this line of markup to the expenseItem component, correct below its attribute definitions.

                <aura:registerEvent name="updateExpense" type="c:expensesItemUpdate"/>

This markup says that our component fires an event, named "updateExpense", of type "c:expensesItemUpdate". Just, wasn't "expensesItemUpdate" the name of the event when we divers it? And what happened to component or application consequence types?

You're correct to think it's a little confusing—information technology actually is a bit of a switch-a-roo. Information technology might assistance to think of "application" and "component" as Aura components framework event types, while the types that come up from the names of events you define are custom event types, or result structure types. That is, when y'all ascertain an outcome, you ascertain a package format. When you register to send an event, yous declare what format it uses.

The process of defining and registering an upshot might notwithstanding seem a bit weird, so let's expect ahead a bit. Hither in expenseItem, we're going to send an upshot named updateExpense. Later in expenseForm, nosotros're going to transport an effect named createExpense. Both of these events need to include an expense to be saved to the server. And then they both use the c:expensesItemUpdate event blazon, or package 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 upward being the aforementioned, the user interface updates are slightly different. And then how does expenses know whether to create or update the expense in the c:expensesItemUpdate bundle? By the name of the event being sent.

Understanding the distinction hither, and how 1 event can be used for multiple purposes, is a lite bulb moment in learning Lightning Components. If you lot oasis't had that moment quite nonetheless, you'll have information technology when you await at the rest of the code.

Before nosotros motion on to handling events, allow's summarize what it takes to ship them.

  1. Define a custom event by creating a Lightning Result, giving it a name and attributes.
  2. Register your component to send these events, by choosing a custom event type and giving this specific use of that type a proper noun.
  3. Burn down the event in your controller (or helper) code past:
    1. Using component.getEvent() to create a specific event instance.
    2. Sending the event with fire().

If you went ahead and implemented all of the lawmaking we just looked at, you can exam it out. Reload your app, and toggle a Reimbursed? checkbox a few times. If you missed a stride, you'll get an mistake, and you should recheck your work. If y'all did everything right…hey, look, the expense changes color to prove its Reimbursed? status, simply equally expected!

This behavior was present before we even started this unit. That'south the consequence of the <lightning:input> component having value="{!v.expense.Reimbursed__c}" prepare. When you toggle the switch, the local version of the expense is updated. But 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 not? We've only done half of the work to create a complete circuit for our event. We take to end wiring the circuit by creating the event handler on the other side. That event handler will take intendance of sending the change to the server, and making the update durable.

Handling an Event

Enabling the expenseItem component to send an effect required three steps. Enabling the expenses component to receive and handle these events requires three parallel steps.

  1. Define a custom event. We've already done this, considering expenseItem is sending the same custom consequence that expenses is receiving.
  2. Register the component to handle the event. This maps the event to an action handler.
  3. Actually handle the event in an action handler.

Since we've already done step i, let's immediately turn to step 2, and register expenses to receive and handle the updateExpense consequence. Like registering to send an event, registering to handle i is a single line of markup, which y'all should add together 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 <aureola:handler> tag, and has an action attribute that sets the controller action handler for the issue. Like when you lot registered the event in expenseItem, hither you set the name and type of the event—though note the employ of the much more than sensibly named upshot attribute for the type.

In other words, there's not much here y'all haven't seen before. 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 part of making this work. All that'due south left is to actually write the action handler!

We'll beginning with the handleUpdateExpense action handler. Here's the code, and be sure to put it riiiiiight under the clickCreate action handler.

                handleUpdateExpense: function(component, effect, helper) {         let updatedExp = event.getParam("expense");         helper.updateExpense(component, updatedExp);     }

Huh. That's interesting. Except for the course validation check and the specific helper office we're delegating the work to, it looks like this action handler is the same every bit handleCreateExpense.

And now permit's add the updateExpense helper function. As we did with the activity handler, brand sure you put this code correct below the createExpense helper function.

                updateExpense: office(component, expense) {         let activeness = component.get("c.saveExpense");         action.setParams({             "expense": expense         });         action.setCallback(this, function(response){             let state = response.getState();             if (state === "SUCCESS") {                 // do nil!             }         });         $A.enqueueAction(action);     },

Two things you should notice right off the bat. Beginning, except for the callback specifics, the updateExpense helper method is identical to the createExpense helper method. That smells similar opportunity.

2nd, near those callback specifics. What gives? How tin can the right thing to do be cypher?

Think about it for a moment. Before, when testing sending the event (if not earlier), we saw that the expenseItem component's colour changed in response to toggling the Reimbursed? checkbox. Remember the explanation? The local copy of the expense record is already updated! And then, at least for the moment, when the server tells the states information technology was successful at updating its version, nosotros don't take to practice anything.

Note that this code but handles the case where the server is successful at updating the expense record. We'd definitely have some work to practice if there was an error. Say, Accounting flagged this expense as non-reimbursable, making it incommunicable to ready this field to true. But that, as they say, is a lesson for another 24-hour interval.

Refactor the Helper Functions

Let's go back to that opportunity we saw to factor out some mutual code. The 2 helper functions are identical except for the callback. Then, let'due south brand a new, more generalized office that takes the callback as a parameter.

                saveExpense: function(component, expense, callback) {         permit action = component.get("c.saveExpense");         activity.setParams({             "expense": expense         });         if (callback) {             activity.setCallback(this, callback);         }         $A.enqueueAction(action);     },

The callback parameter is optional. If it'due south there, we'll pass it along to activity. Elementary. And now nosotros tin reduce our result-specific helper functions to the following lawmaking.

                createExpense: part(component, expense) {         this.saveExpense(component, expense, office(response){             let state = response.getState();             if (land === "SUCCESS") {                 let expenses = component.get("v.expenses");                 expenses.push(response.getReturnValue());                 component.set("v.expenses", expenses);             }         });     },     updateExpense: function(component, expense) {         this.saveExpense(component, expense);     },

createExpense is merely a little shorter, merely it'south exclusively focused on what to do when the response comes dorsum (the callback). And wow, updateExpense is a i-liner!

Refactor the Add Expense Grade

That little refactoring practice was so satisfying, and using events was so (we're deplorable) electrifying, allow's do information technology once more, but bigger. More cowbell!

This next chore involves extracting the Add Expense form from the expenses component, and moving it to its own, new component. Extracting the course markup is easy enough, a simple copy-and-paste do. But what else moves with information technology? Before we start moving pieces around willy-nilly, permit'due south think about what moves and what stays.

In the current design, the course'southward action handler, clickCreate, handles input validation, sending the asking to the server, and updating local state and user interface elements. The course will still demand an action handler, and should probably still handle form validation. Just we'll plan on having the remainder stay behind, considering we're keeping our server request logic consolidated in the expenses component.

And then there's a little (but only a little!) teasing autonomously to do at that place. Our plan, so, is to kickoff by moving the form markup, and then move as little as possible later on that to brand it work correctly. We'll refactor both components to communicate via events, instead of via direct access to the expenses assortment component aspect.

Allow's get started!

In the chief expenses component, select everything between the two <!-- CREATE NEW EXPENSE --> comments, including the beginning and ending comments themselves. Cutting information technology to your clipboard. (Yes, cut. We're committed.)

Create a new Aura component, and proper name it "expenseForm". Paste the copied Add Expense form markup into the new component, between the <aura:component> tags.

Dorsum to the expenses component. Add the new expenseForm component to the markup. That section of expenses should expect like this.

                <!-- NEW EXPENSE Form -->     <lightning:layout >         <lightning:layoutItem padding="around-small" size="half-dozen">             <c:expenseForm/>         </lightning:layoutItem>     </lightning:layout>

At this point, yous can reload your app to run across the changes. There should exist no visible changes. Only, unsurprisingly, the Create Expense button no longer works.

Let's get quickly through the rest of the moving things effectually part.

Next, move the newExpense aspect from the expenses component to the expenseForm component markup. This is used for the form fields, so it needs to exist in the grade component. It moves over with no changes required, then just cut from i and paste in the other.

In the expenseForm component, create the controller and helper resources.

Move the clickCreate action handler from the expenses controller to the expenseForm controller. The button is in the class component, and so the action handler for the button needs to be there, besides. Believe it or non, this likewise needs no changes. (You might begin sensing a theme here.)

Now we need to brand a couple of actual changes. Just these will exist familiar, considering nosotros're simply adding consequence sending, which nosotros did before for expenseItem. And expenseItem, you lot'll recall, also sends an effect with an expense payload, which is handled by the expenses component.

In the expenseForm helper, create the createExpense part.

                createExpense: function(component, newExpense) {         permit createEvent = component.getEvent("createExpense");         createEvent.setParams({ "expense": newExpense });         createEvent.fire();     },

This looks very much like the clickReimbursed action handler in expenseItem.

If a component is going to send an event, it needs to annals the event. Add the following to the expenseForm component markup, just below the newExpense attribute.

                <aura:registerEvent proper noun="createExpense" type="c:expensesItemUpdate"/>

At this point, nosotros've washed all the work to implement the expenseForm component. You should exist able to reload the app, and the form now "works" in the sense that there are no errors, and you should see the appropriate course letters when y'all enter invalid information. If you're using the Salesforce Lightning Inspector, you can even see that the expensesItemUpdate effect is being fired. All that'south left is to handle information technology.

Before we handle the issue, please do notice how like shooting fish in a barrel this refactoring was. Almost of the lawmaking didn't change. There's a total of half dozen lines of new code and markup in the preceding steps. It's unfamiliar to do this work today, but exercise it a few times, and you realize that you're just moving a chip of lawmaking around.

OK, let's stop this. The expenseForm fires the createExpense outcome, but nosotros likewise need the expenses component to catch it. First we register the createExpense event handler, and wire information technology to the handleCreateExpense action handler. In one case again, this is a unmarried line of markup. Add together this line right above or beneath the updateExpense event handler.

                <aura:handler proper name="createExpense" event="c:expensesItemUpdate"         action="{!c.handleCreateExpense}"/>

Finally, for the terminal pace, create the handleCreateExpense activity handler in the expenses controller. Add this code right above or beneath the handleUpdateExpense action handler.

                handleCreateExpense: function(component, consequence, helper) {         let newExpense = event.getParam("expense");         helper.createExpense(component, newExpense);     },

Yep, that simple. All of the work is delegated to the createExpense helper office, and that didn't move or change. Our handleCreateExpense action handler is just at that place to wire the correct things together.

And with that, we're finished showing how to loosely couple components using events. Create and fire the issue in one component, catch and handle information technology in some other. Wireless circuits!

Bonus Lesson—Minor Visual Improvements

Before nosotros head off into the dusk, or rather, the challenge, hither is a modest visual improvement.

We'd like to amend the layout of our app a little bit, by adding a few container components. This last bit also gives you lot an opportunity to encounter the full expense component after all our changes. In the expense component, replace the expensesList markup with the following.

<lightning:layout>     <lightning:layoutItem padding="around-small" size="6">         <c:expensesList expenses="{!5.expenses}"/>     </lightning:layoutItem>     <lightning:layoutItem padding="around-small-scale" size="6">         Put something cool here     </lightning:layoutItem> </lightning:layout>

The effects of the changes are to add some margins and padding, and make the expenses listing more narrow. The layout leaves room to put something over there on the right. In the next unit of measurement, we'll suggest a couple of exercises you could do on your ain.

lardnermargance.blogspot.com

Source: https://trailhead.salesforce.com/content/learn/modules/lex_dev_lc_basics/lex_dev_lc_basics_events