trailhead

Take Action with Events and Functions

Learning Objectives

After completing this unit, you’ll be able to:

  • Define functions
  • Distinguish between function declaration and function expressions
  • Invoke functions
  • Pass and assign functions
  • Describe uses of functions and events in Lightning components

When it comes to making stuff happen in JavaScript it’s all about events and functions.

Remember our model of the runtime? Let’s extend that a bit. 

With JavaScript in the browser, events are all over the place. Parts of the DOM emit events that correspond to what that DOM object does. Buttons have click events, input and select controls have change events, and virtually every part of the visible DOM has events for when the mouse cursor interacts with it (like passing over it). The window object even has event handlers for handling device events (like detecting the motion of mobile devices). 

To make something happen in a web page, functions get assigned to these events as event handlers. 

To reiterate, DOM events and other events related to the browser environment are not actually part of the core JavaScript language, rather they are APIs that are implemented for JavaScript in the browser. 

When an event is emitted, a message is created in the engine. It is these messages that are placed in the event queue we talked about earlier. 

Messages in the queue and frames in the stack.Once the stack is free, the event handler is invoked. This creates what’s referred to as a frame on the call stack. Each time one function invokes another, a new frame is added to the stack, and when complete, it is popped off the stack, until finally the frame for the actual event handler is popped, the stack is empty, and we start all over again. 

Defining and Assigning Functions

In JavaScript functions are essentially special objects. As objects, they are first-class members of JavaScript. They can be assigned as the values of variables, passed into other functions as parameters, and returned from functions. 

There are two essential phases in the life of a function: definition and invocation.  

When function is declared, its definition is loaded into memory. A pointer is then assigned to it in the form of a variable name, parameter name, or an object property. It should be no surprise, however, that there are several different syntaxes to do this. 

Function Declaration

A declaration is a statement that uses the function keyword to create a function. In fact we’ve seen it already when we were looking at our object constructor. That constructor is a function. But constructor functions are a bit special, so let’s step back and talk about plain old functions and see how they work: 

// declare function
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// call function
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

In this code sample, function is followed by the name of the function, with parameters enclosed in the parentheses. 

This works fine, but there’s some implicit stuff happening. First of all, the function name becomes the variable name. It also implicitly assigns the variable to the enclosing context. Finally, you can call this function before it is declared, such as below where calculateGearRatio is invoked the line before the declaration. 

// call function
let gearRatio = calculateGearRatio(42, 30);
// function is declared after the line it is called
// this is allowed in function declaration
function calculateGearRatio(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
console.log(gearRatio); // 1.4

Function Expressions

Function expressions accomplish the same thing as declarations more explicitly. 

const calculateGearRatio = function(driverGear, drivenGear){
  return (driverGear / drivenGear);
}
// the rest works the same
let gearRatio = calculateGearRatio(42, 30);
console.log(gearRatio); // 1.4

In this instance we have an explicitly assigned variable. Since we’ve named the pointer, we can drop the function name after the function keyword. The only catch here is that the function must be declared prior to it being invoked. 

Notably, function expressions are also used to assign functions as members of objects. Recall when we assigned the changeGear function to Bike.prototype?  

Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

Returning a Function

Since functions are first-class objects, another way to declare a function is when a function returns another function. This pattern is often referred to as a factory function. 

// when invoked, this function will assign a function
function gearFactory(){
  return function(driverGear, drivenGear){
    return (driverGear / drivenGear);
  }
}
// calculateGearRatio can now be invoked as a function
const calculateGearRatio = gearFactory();
// and all the rest

While the above example is simplistic, it’s valid. Factory functions are useful for one-off reusable functions, especially to capture variable references in a closure. We talk about closures in a later unit. 

Anonymous Functions

There are many APIs in JavaScript that require you to pass a function for them to work. Say, for instance, you have an array, and you want to create a new array derived from the values of that array. In this case you would probably use the Array.map function.

let myArray = [1, 5, 11, 17];
let newArray = myArray.map( function(item){ return item / 2 } );
console.log(newArray); // [0.5, 2.5, 5.5, 8.5]

In this snippet, myArray.map takes in a single parameter: a function that is executed once per item in myArray

This function is never reused. It is declared as an argument (with no name...thus “anonymous”) passed into the function, and is executed in the internals of the implementation of the map function. Anonymous functions (also called lambdas in some languages) are commonplace in JavaScript. 

Function Invocation

Once you’ve declared your function, you’ll probably want to get around to invoking it. When a function is invoked, a few things happen. 

Remember, the first thing is a new frame is pushed onto the stack. Then an object containing its variables and arguments is created in memory. The this pointer is then bound to the object along with a few other special objects. Values passed into arguments are then assigned, and finally the runtime begins to execute the statements in the body of the function. 

The binding of this has one important exception which we’ll revisit in the unit on Asynchronous JavaScript. 

Invocation Versus Assignment

When working with functions, one potential source of confusion to those new to JavaScript is whether you are assigning/passing a function or invoking it. It all comes down to whether you use the ()

Consider our bike object’s calculateGearRatio function. 

let bike = {
  ...,
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
    rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  ...
}

Now consider these two different ways to access the calculateGearRatio function.  

// invoke function and assign value to ratioResult
let ratioResult = bike.calculateGearRatio();
// assign calculateGearRatio function to a new pointer
const ratioFunction = bike.calculateGearRatio;

In the first instance, calculateGearRatio is invoked with the result returned from the function being assigned (in this instance as a primitive value) to the ratioResult variable. On the other hand ratioFunction has simply been assigned or pointed to the calculateGearRatio function. You could then turn around and invoke it as ratioFunction

There are reasons to assign a function to another pointer, particularly as a parameter of another function like with the Array.map() function. But any function using a this reference risks breaking, since this can point to different things at different times. More on that later. 

Functions as Event Handlers

If you want a function to fire as the result of an event, it needs to be wired up to that event. Doing so makes that function an event handler. In vanilla HTML/JavaScript, the event handler function definition needs to include a single argument: a pointer to the event that fired it. 

var handleClick = function(event) {
}

Each event has properties that tell you what you need to know about that event to deal with it. For example, for click you can detect data about the click (the event type, what element fired it, coordinates of the click, and so on).  

var handleClick = function(event) {
  console.log(event.type);  // click
  console.log(event.currentTarget); // the thing you clicked
  console.log(event.screenX); // screen X coordinate
  console.log(event.screenY); // screen Y coordinate
}

Assigning Event Handlers via DOM APIs

In simple web pages you may occasionally see explicit assigned event handlers in the HTML. 

<button onclick="handleClick(event)">
  Click to Go
</button>

However modern web applications rarely use event binding in HTML. Instead, the DOM API is prefered, specifically the JavaScript Element.addEventListener() function. 

First you need reference to the HTML element. Below we’ve added an id attribute to our button and removed the onclick attribute. 

<button id=”clicker”>

Now we reach into the DOM, get the reference to the button, and assign the event listener, handleClick, by passing it in as a value (note, no parentheses). 

let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

Using the DOM API gives the developer flexibility to make the UI highly interactive and responsive to what the user is doing. The developer can also remove an event listener if functionality needs to be turned off. 

button.removeEventListener("click", handleClick);

You will also see anonymous functions added as event listeners. 

button.addEventListener("click", function(event){
  //...anonymous function body...
});

Bear in mind, anonymous functions can’t be removed using removeEventListener, as there is no pointer to pass in to identify the function. 

Events and Functions in Lightning

There are three places where functions are defined in a Lightning component: the controller, helper, and renderer. 

JavaScript Artifacts in a Lightning Component

The controller is where you define the event handlers for your component. It’s also helpful to understand that each instance of your component that is rendered gets its own controller object. The code below shows the event handler binding for the onchange event. 

<lightning:select onchange="{!c.handleSelectChange}" value="{!v.currentFrontGear}" label="Front">
  <aura:iteration items="{!v.frontGears}" var="item">
    <option value="{!item.index}">{!item.value}</option>
  </aura:iteration>
</lightning:select>

While this looks like statically assigning an event handler, it really isn’t. Under the covers, the Lightning component framework turns this markup into instructions in JavaScript. 

Lightning Component event handler functions also have a different signature. The function is passed three arguments that point to the component itself, the event that fired the function, and the helper object. From there the event handler goes to work. 

handleSelectChange : function(component, event, helper) {
// update gear ratio on gear change
  helper.setGearRatio(component);
}

To access reusable code, the helper parameter points to the the component’s helper object. Helper functions can contain whatever parameters you need them to according to the logic you are trying to implement. 

One important point to bear in mind is that all instances of a component share a single helper object. So don’t try to track state in the helper unless you intend for that state to be shared across all instances of your component. You can see an example component that demonstrates that here

Surfacing a Function to the Outside World with aura:method

In general, a component’s functions are private. However if you want to expose a function to be invoked directly from your component, use the aura:method marker and bind your controller function to it. 

<aura:method name="reload" action="{!c.handleInit}" access="public" description="Allow explicit reload from other components"/>

Even though it is possible to surface a function as a method, this creates a tightly coupled relationship between two components. This is makes sense when you want a parent component to invoke logic in a child component. 

Lightning Events and Handlers

The Lightning Event service is a key part of making loosely coupled components in Lightning that work not just in code, but in Lightning App Builder. There’s too much to cover here, but again, there are resources on Trailhead for you to dive into to learn this vital part of Lightning components. 

Resources

Code Sample Project for This Module

JavaScript Functions

Event Handlers

Lightning Component Basics

Build Reusable Lightning Components Trailhead Project

retargeting