Handle Actions with Controllers

Learning Objectives

After completing this unit, you’ll be able to:
  • Create a client-side controller to handle user actions.
  • Read values from component attributes.
  • Read values from user interface controls in your component.
  • Write controller code in JavaScript that changes the user interface.

Handling Actions with Controllers

Up to now, we’ve worked only with XML-style markup. Up to now, the only way to have our component’s output change was to change that markup. Up to now, our components didn’t react to user input. Up to now, we haven’t written any JavaScript.

That all changes in this unit.

To get started, let’s look at a very simple component, and imagine what it needs to do to be able to handle its simple behavior.

Message of the day: You look nice today button, Today is going to be a great day button

This is helloMessageInteractive, and it’s hard to imagine a simpler component that “does something.” It’s a bit of static text, a (currently blank) message, and two buttons. Here’s the code:

    <aura:attribute name="message" type="String"/>
    <p>Message of the day: {!v.message}</p>
        <lightning:button label="You look nice today."
        <lightning:button label="Today is going to be a great day!"

This should look familiar. All we’ve really done is added two <lightning:button> components to helloMessage. When you click a button, the message of the day is updated.

Well, not quite yet. If you’ve already entered the code and tried it yourself, you’ve noticed that when you click either of the buttons you get an error message.

There is a problem

We’ll be the first to admit that not every error message you’ll see in Lightning Components is as helpful as you might hope. But this one is! It says that there’s no controller action named “handleClick”. Where does “handleClick” come from? It comes from the expression we assigned to the onclick attribute on each of the two <lightning:button> tags:


Given that this is a button, you can probably guess that the onclick attribute is how you assign a behavior to the button for when it’s clicked. But what have we assigned? An expression, {!c.handleClick}, that’s maybe a bit mysterious.

It’s actually pretty simple. Just like the v.message expression from earlier, c.handleClick is a value provider, c, with a property, handleClick. c is the value provider for the component’s client-side controller, and handleClick is a function defined in that controller. So, {!c.handleClick} is a reference to an action handler in the component’s controller.

c.handleClick: c is a value provider for the component controller, with the property handleClick, a function defined in that controller

Uh, What’s a Controller?

Whoops! A controller is basically a collection of code that defines your app’s behavior when “things happen,” whereby “things” we mean user input, timer and other events, data updates, and so on. If you look up “Model-View-Controller” on any number of developer sites, you’ll get various definitions. For our purposes, for Lightning Components, a controller is a resource in a component bundle that holds the action handlers for that component. And action handlers are just JavaScript functions with a particular function signature.

Beyond the Basics

We talk a lot about controllers in this unit, and we know that the component itself is a view. We even mentioned the MVC, or Model-View-Controller, design pattern that’s so common in web app frameworks. Is Lightning Components built on the MVC pattern?

In a word, no. There are similarities, to be sure, but it would be more correct to say that Lightning Components is View-Controller-Controller-Model, or perhaps View-Controller-Controller-Database.

Why is “controller” doubled up in that pattern name? Because when interacting with Salesforce, your components will have a server-side controller in addition to the client-side controller we’ve worked with in this unit. This dual controller design is the key difference between Lightning Components and MVC.

What’s the distinction between “model” and “database”? In traditional MVC, the model is a programmatic abstraction (usually a class) between the underlying data storage (usually a relational database) and the rest of the application. In Lightning Components, there’s no Apex class that directly stands in between @AuraEnabled controller methods and DML operations. But then again, sObjects are already an abstraction between your Apex code and the underlying storage layer. You can add calculation fields, validation logic, and even add fully programmatic behavior in the form of triggers. So, is it a database or a model? We say po-TAY-tow but it’s totally cool if you want to go with po-TAH-tow.

Confused? Excited? We’ll get you sorted on the details of server-side controllers in a later unit.

Let’s look at the helloMessageInteractive controller in more detail, and make that explanation a bit more concrete.

    handleClick: function(component, event, helper) {
        let btnClicked = event.getSource();         // the button
        let btnMessage = btnClicked.get("v.label"); // the button's label
        component.set("v.message", btnMessage);     // update our message

Controller resources have an interesting format. They are JavaScript objects that contain a map of name-value pairs, where the name is the name of the action handler and the value is a function definition.

Action Handlers

The combination of name-value pair and specific function signature is an action handler. You’ll hear or see the terms action handler, controller action, and controller function used interchangeably, and for the most part that’s correct. They almost always refer to the same thing. (We’re not going to worry about the exceptions in this module.)

Don’t worry too much about the special format of the controller resource. When you click the CONTROLLER button in the Developer Console, you’ll get a controller resource with an example action handler already added. The one trick is—and you will get syntax errors if you forget—you need to put commas between action handlers. This is just basic JavaScript syntax, and we’ll see the specifics in a bit.

The actual handleClick function is only four lines of code, but they might seem hard to understand at first. At a high level, it’s simple: When the button is clicked, its action handler gets called (1). In the action handler, the controller gets the button that was clicked, pulls the label text out of it, and then sets the component’s message attribute to that text (2). And the message of the day is updated (3). Today is going to be a great day!

In the action handler, the controller gets the clicked button text, then sets the component message attribute

Simple, right? Well…

Because this is super important, let’s break it down line-by-line.

handleClick: function(component, event, helper) {

The action handler name, followed by an anonymous function declaration. The important thing here is the function signature. While it’s not technically required, you should always declare your controller functions to take these three parameters. We’ll talk more about them as we go, but for now, these parameters represent:

  • component—the component. In this case, it’s helloMessageInteractive.
  • event—the event that caused the action handler to be called.
  • helper—the component’s helper, another JavaScript resource of reusable functions.
    let btnClicked = event.getSource();         // the button

Remember that handleClick is connected to our <lightning:button> tag and its onclick attribute. The event, then, is someone clicking the button. Inside that event it has the notion of a source, the thing that generated the event, which is the button itself. So, calling event.getSource() gets us a reference to the specific <lightning:button> that was clicked.

    let btnMessage = btnClicked.get("v.label"); // the button's label

What do we do now that we have a reference to the button? We look inside it and get its label, which is set on the <lightning:button> in the component markup. For example, <lightning:button label="You look nice today." ... >.

Let’s think about that a bit more. We don’t have the definition of <lightning:button> in front of us, but label is just another attribute, much like the message attribute we added to helloMessageInteractive. You can call get() on any component and provide the name of the attribute you want to retrieve, in the format v.attributeName. The result is the attribute value.

Note that, as in component markup, v represents the view, the component itself—but in this case, it’s the <lightning:button> child component, not helloMessageInteractive! Think of it this way. btnClicked.get("v.label") taps on the shoulder of whatever component btnClicked is and says “Hey, give me v.label”. That component thinks “v is me,” looks inside itself, and returns the value of its label attribute.

So now that we have a text string retrieved from the button, we just have one step left: to change our message attribute to the new message text. Unsurprisingly, just as get() reads a value from a component, set() writes a value.

    component.set("v.message", btnMessage);     // update our message

However, let’s notice one important difference. We called get() on btnClicked, the <lightning:button> that’s inside helloMessageInteractive. We’re calling set() on component—the helloMessageInteractive component itself. This is a pattern you’ll repeat in virtually every component you create: get values from child components, maybe do some processing, and set values in the component itself.

The Aura Components View-Controller Programming Model

OK, gut check time. Is this making sense? For sure? If you just think so, make sure you’ve actually created the helloMessageInteractive component using the preceding code. It’s one component, and copy/pasting the code takes two minutes, but being able to play with it is essential for understanding handling actions.

What you’ve done here might seem very simple, because it’s not very many lines of code. But these lines of code illustrate some of the fundamental concepts of building apps with Aura components.

You can think of hooking up components to action handlers as “wiring them up.” Think of helloMessageInteractive as a simple electrical circuit. There are switches, and there are light bulbs. (The Lightning Components framework supplies the power.) By itself, a switch might make a nice clicking sound, but until you wire it up, it’s not very functional. You can have the trendiest Edison-style light bulb, but until you wire it up, it doesn’t illuminate anything.

And so it is with Aura components. Way back ago, we said that the different resources in a component bundle are “auto-wired” to each other. And that’s true: the wiring takes the form of the v and c value providers. They’re automatically created and made available within your components, so your controller can reference the component and vice versa. But this auto-wiring takes place only at a high level—in helloMessageInteractive, between the component .cmp resource and the controller .js resource. This is the green arrow in the following illustration.

helloMessageInteractive and its controller are auto-wired

The wiring to hook up a specific <lightning:button> component to a specific action handler—that is, wiring things that generate events, such as buttons, to the things that handle events, like a specific controller function—that’s wiring you need to do yourself. Which, in fact, you just did yourself! These are the red arrows needed to complete a working circuit.

Adding {!c.handleClick} to the onclick attribute of a <lightning:button> component (1) wires it up to the specific action handler. Calling component.set("v.message", newMessage) (2) wires up the result of that action handler to the component’s message attribute. Which is itself wired up to the {!v.message} expression.

You can further think of the onclick event as an electron flowing along the circuit you’ve created. If you haven’t created a complete circuit, the event doesn’t go anywhere, and nothing happens. When you start writing your own components, keep this in mind. Do you have a complete circuit? Are you sure? When in doubt, it sometimes helps to sketch it all on a whiteboard or paper, and confirm every connection.

You’ll wire together components, events, and handlers, at low and high levels, so often you’ll feel like an electrician. (Or, given the name of the framework, maybe Ben Franklin.) Wiring things together is fundamental to the Aura Component programming model.

So, let’s do it some more. After all, practice makes perfect.

Function Chaining, Rewiring, and Simple Debugging

Our first version of handleClick was three lines of code, because we broke each step in the get-process-set pattern into separate lines. You can use something called function chaining to collapse those into fewer lines. Since you’ll probably see this in other Lightning Components code, let’s give it a whirl ourselves. Add the following additional code to your helloMessageInteractive controller, after handleClick.

    handleClick2: function(component, event, helper) {
        let newMessage = event.getSource().get("v.label");
        component.set("v.message", newMessage);
    handleClick3: function(component, event, helper) {
        component.set("v.message", event.getSource().get("v.label"));

Whoopsie! Did you just get a syntax error when trying to save? Remember earlier when we said you need to add commas between your action handlers? That’s the issue here. Add a comma after the final brace (“ }”) of handleClick, as you can see at the end of handleClick2 in the preceding snippet.

These action handlers do exactly the same thing as handleClick, in fewer lines of code. They do this by skipping intermediate variables, often by “chaining” directly to the next function call, by adding that call to the end of the previous one, separated by a period. (This concept is most clear when looking at the differences between handleClick and handleClick2.)

Which style you prefer is a matter of personal taste, and perhaps your organization’s coding style. Your humble author prefers handleClick2, separating the get and set, but not bothering with a variable for the button, which we only need for its label text.

Of course, you can’t verify that the new action handlers work until you wire a <lightning:button> to use one of them, by setting the onclick attribute to {!c.handleClick2} or {!c.handleClick3}. Think of this as disconnecting the wires from one light bulb, and then attaching them to a different light bulb. It’s that easy!

At that point, you reload the app, click one of the rewired buttons, and…well, it’s the same by design, isn’t it? How can we even tell which action handler is being called?

Sometimes simple is best. Let’s add a bit of logging to one of the action handler functions:

    handleClick2: function(component, event, helper) {
        let newMessage = event.getSource().get("v.label");
        console.log("handleClick2: Message: " + newMessage);
        component.set("v.message", newMessage);

Now if you wire up a <lightning:button> to handleClick2, you’ll see a log message in your browser’s JavaScript console whenever you click it.

Yes, there are more sophisticated debugging tools, but printing things to the console is a time-honored debugging technique. If you want to output an object of some sort, wrap it with JSON.stringify(yourObject to get even more useful details. When you’re after a quick peek, console.log() is your friend.

We won’t cover those more sophisticated debugging tools and techniques here, but see Resources for some great tools and instructions.

OK, helloMessageInteractive was stupid simple and has a hard-coded (and relentlessly positive) attitude. In the next unit, we’ll work with something more complex, and learn how to capture real user input. And since people in the real world aren’t always so positive, we’ll learn how to validate their input, too.

Keep learning for
Sign up for an account to continue.
What’s in it for you?
  • Get personalized recommendations for your career goals
  • Practice your skills with hands-on challenges and quizzes
  • Track and share your progress with employers
  • Connect to mentorship and career opportunities