Course: JavaScript

Progress (0%)

JavaScript Events - Basics

Chapter 60 33 mins

Learning outcomes:

  1. Intro to terminology: event's name and target
  2. What are event handlers
  3. How to set up event handlers
  4. Event-handling properties, e.g. onclick
  5. The EventTarget interface
  6. Event handler's this binding
  7. HTML event-handling attributes

Event's name (or type)

Every event in JavaScript is denoted by a name. This name is typically a verb, since an event represents an action (and a verb also represents an action).

Let's take the example of a mouse click. In JavaScript, the click action is referred to as the click event. So when we click on a certain element in a webpage, the click event occurs. Simple.

As another example, and not far away from this basic click action, the click of the mouse's right button is referred to as the contextmenu event. So when we right-click the mouse, the contextmenu event occurs.

The word 'contextmenu' comes from the fact that when the right-click action occurs somewhere on a webpage, a menu box is displayed to the user. We'll learn more about it in the JavaScript Mouse Events chapter.

Sometimes the event's name is also referred to as the event's type.

In this regard, henceforth, the type of the click event would be, well, click. Similarly, the type of the right-click event would be contextmenu.

The name and type of an event are two words referring to the exact same idea.

Knowing this second term is important since the Events API provides us with a type property on an event object to inspect about the type of an event; or in other words, inspect its name. We'll see the details to this in a later chapter in this very unit.

Keep in mind that these names are very important for us to remember because we'll eventually need them when handling the underlying events.

Now to set a convention from this point onwards, throughout this course, we'll refer to events in the following way, taking the example of the click event: click — just like any other piece of code. This will help us easily distinguish when we are talking about an action leading to an event vs. when we are talking about that very event specifically from the perspective of code.

Event's target

Apart from the name, every single event has an underlying target.

The target is simply an object on which the event takes place.

For instance, if we click on a <div> element, then the target of the click event would be that <div> element. Similarly, if a given <script> loads completely, then the target of the consequently occuring load event would be that <script> element.

We'll learn more about the load event in the JavaScript Load Events chapter.

There can never be an event without a target. It's that simple.

Typically, as we shall see throughout this unit, the targets of events are instances of Element. However, there are times when the targets might be other kinds of objects, such as instances of Window, XMLHttpRequest, FileReader, IndexedDBRequest, and so on and so forth.

Event's handler

When an event occurs on a webpage, the corresponding JavaScript engine gets notified of it and then a corresponding block of code gets executed, if there is any.

Such a block of code is set up the developer in the form of a function. This shouldn't be any surprising because it's only the function construct that can hold a block of code and execute it on demand.

This function is what we refer to as the event's handler.

An event's handler function, or simply handler, is a function set up by the developer to handle the event, when it occurs.

Sometimes, the handler is also called the event's listener function, or simply listener.

Event handlers are automatically invoked by the JavaScript engine when their underlying event occurs.

And when that does happen, they even get provided with an argument object. This argument is commonly known as the event object and serves to host a plethora of properties and methods to allow the developer to inspect details of the occured event.

We'll explore event objects in the next chapter JavaScript Events — Event Objects.

Let's now see how to set up event handlers in JavaScript, which is perhaps the most important thing in the entire events ecosystem.

Event-handling properties

In JavaScript, there are essentially three ways to setup event handlers:

  1. Use event-handling properties
  2. Use the EventTarget interface
  3. Use event-handling HTML attributes

We'll consider the very first option for now, leaving the last two to be discovered in detail later on in this chapter.

Many interfaces in JavaScript are extended by these so-called event-handling properties. They begin with the word 'on' followed by the name of an event, all in lowercase, and simply serve one purpose — to hold a handler for the given event.

For instance, taking the click event as an example, its corresponding event-handling property is onclick. This property is available on all objects that can potentially receive a mouse click on a web page. To be precise, onclick is defined on the HTMLElement interface.

As a quick example, if we wish to handle the click event on the following <button> element,

<button>Click me!</button>

we'd write something as follows:

var buttonElement = document.querySelector('button');

buttonElement.onclick = function() {
   // Handle the click event over here.
}

Here, the function assigned to the onclick property is what we'll call the click event's handler.

Now, since onclick is just another JavaScript property, we can even assign it a function identifier, instead of assigning it an anonymous function expression directly, as demonstrated below:

var buttonElement = document.querySelector('button');

function buttonClickHandler() {
   // Handle the click event over here.
}

buttonElement.onclick = buttonClickHandler;

Note that we don't have to call the function buttonClickHandler(); just pass its reference to the onclick property, which will then automatically be called by the JavaScript engine on the occurence of a click.

In the next section, we'll consider all these examples in more detail.

Event-handling properties are extremely well-supported across a majority of browsers, including the ancient ones. This shouldn't be surprising at all, since they are part of the legacy DOM, which itself has an amazing browser support.

A simple example

In the chapter CSSOM Viewport, we saw how to set up a click event handler on a <button> element. Let's now take a look at another such example, this time with the aim of exploring it in even more detail than before.

Consider the following HTML:

<button>Show alert</button>

Just a single button as the body of the web page.

Now what we want to do here is to show up an alert box when the user clicks this button.

How to do this? Well, it's really easy to do.

First, we ought to select the <button> element. Then, we need to set up a handler function for its click event. For now, we'll do so using the onclick property of the <button> element. Finally, inside the handler, we'll make the desired call to alert().

And that's it.

Here's the complete code:

var buttonElement = document.querySelector('button');

buttonElement.onclick = function() {
   alert('You clicked the button!');
}

We can now click the button and witness the alert box showing up.

Live Example

Great!

It doesn't hurt to know a little more about how exactly does the alert get made in the code above after the instant where we click the button.

Here is a high-level overview of the steps involved:

  1. A click is made over the <button> element.
  2. The OS (Operating Sytem) notices a change in the mouse's state and realizes that it's because of a click. It passes this information to the web browser software which then extracts further information from the OS, most importantly the coordinates of the pointer at the time of the click.
  3. Using these coordinates and the layout of the page (i.e. what's show currently in the viewport), the browser determines which element was there at the position of the pointer.
  4. In our case, since the mouse pointer was atop the button, the browser determines that it was the <button> element there where the pointer was at the time of the click. Consequently, it dispatches the click event on the <button> element.
  5. During this stage, all the registered click handlers of <button> are invoked one after another. In our case, we had only one onclick handler set up and so that's only what gets called.
  6. The handler makes a call to alert(), which leads to an alert message in the browser. Finally, once the alert() call completes (which is when we close the alert box), the handler reaches completion.

And this completes the whole lifecycle for the click event since the moment it originates in the <button> element.

Precisely speaking, there are many many phases skipped in this list but that's only for the sake of brevity. If we were to add everything right now, the whole process of firing and subsequently handling an event would become really intimidating.

We'll surely understand each and every step involved in this whole process but only gradually as this unit proceeds, in order to keep everything simple and sound.

The EventTarget interface

The property-based approach above to set up event handlers works well in all but some complex cases. These complex cases are when we want to use more than one event handler.

For instance, consider the following hypothetical JavaScript code:

var buttonElement = document.querySelector('button');

function handler1() { /* ... */ }
function handler2() { /* ... */ }

buttonElement.onclick = handler1;
buttonElement.onclick = handler2;

We wish to set up two click event handlers here on the <button> element that should both execute when the button is clicked.

However, we can't assign them one after another to the <button>'s onclick property, since the second assignment, to handler2, overrides the first one.

With the onclick approach, the only way to use multiple handling functions is by manually calling them inside one single function and then using this function as the handler function, as shown below:

var buttonElement = document.querySelector('button');

function handler1() { /* ... */ }
function handler2() { /* ... */ }

buttonElement.onclick = function() {
   handler1();
   handler2();
}

However, this could only work when we have access to the handling functions upfront.

Suppose that the handler is obtained from another program, or worse yet, another program itself wants to add its own handlers in addition to your handlers. In both these cases, it would be totally impossible for us to set up all the handlers by directly working with onclick.

Absolutely, no way!

And that's where the EventTarget interface comes to the rescue.

The DOM Level 2 standard introduced the EventTarget inteface as another, more mature and powerful, means to work with events in JavaScript besides the legacy DOM approach of using event-handling properties.

The interface defines two really basic, yet extremely flexible, methods to set up and remove event handlers from given target objects. They are addEventListener() and removeEventListener(), respectively.

addEventListener()

As the name suggests, the addEventListener() method is used to add an event listener for the target object it's called on.

Here's the syntax of addEventListener():

targetObj.addEventListener(name, listener)

targetObject is an instance of EventTarget on which we wish to handle a given event, name is a string representing the name of the event (a.k.a. its type), and listener is the listener function (a.k.a. the handler function) for the event.

Recall from the HTML DOM chapter, all nodes in the DOM tree are instances of the Node interface, which inherits from the EventTarget interface. Thus, all element nodes have the methods addEventListener() and removeListener() available to them.

Let's consider an example — the same one we saw above, showing an alert box, just this time utilizing addEventListener() instead of onclick.

In the following code, we set up a click handler on the <button> element in order to make an alert when it's clicked:

var buttonElement = document.querySelector('button');

buttonElement.addEventListener('click', function() {
   alert('You clicked the button!');
});

Notice the differences here from the snippet above.

Let's understand what's happening here:

  1. First, we select the <button>, as before.
  2. Next, we invoke addEventListener() on the selected element in order to set up a click handler.
  3. The first argument is 'click' as we want to listen for the click event occuring on the given button.
  4. The second argument is the handler function, which is the exact same as before.

And that's it!

Live Example

removeEventListener()

As with the event-handling property approach that we saw above, it's possible to remove an event handler set up via addEventListener(). That is by using removeEventListener().

However, unlike the former approach, where we ought to set an event-handling property to null in order to remove the corresponding handler, we need to pass the handler's reference to removeEventListener() that we wish to remove.

Here's the syntax of the method:

targetObj.removeEventListener(name, listener)

The first argument is the name of the event whose handler we wish to remove from targetObj while the second argument is a reference to the handler function, provided at the time of calling addEventListener(), that we wish to remove.

removeEventListener() requires an existing function!

Making intuition of the second argument here ain't that difficult. Internally, the EventTarget interface maintains a list of handler functions associated with a given event.

When we wish to remove a particular handler function, there is no way for the interface to figure out that handler function without a reference to it. Once the reference is there, removeEventListener() searches for the handler function in the list and then removes it once it's found there.

Simple?

Henceforth, if we plan to remove an event listener later on in our code, then it's utmost important to keep its reference with us until we remove it from the target object.

Consider the example below:

var buttonElement = document.querySelector('button');

function clickHandler() {
   alert('You clicked the button!');
   buttonElement.removeEventListener('click', clickHandler);
}
               
buttonElement.addEventListener('click', clickHandler);

After the button is clicked for the very first time, removeEventListener() is used to remove its click handler and thereby prevent any further alerts from being made upon its click.

Live Example

Notice that in both the calls, i.e. to addEventListener() and to removeEventListener(), we pass the same function in the second argument. This is not just a nice thing, but also a requirement, as mentioned at the start of this discussion.

If we had added the event handler as follows:

var buttonElement = document.querySelector('button');

buttonElement.addEventListener('click', function() {
   alert('You clicked the button!');
});

without storing it in some kind of an identifier, there would be no way for us to remove it anyhow!

Event handler's this binding

We've seen the this keyword in great detail in the JavaScript Functions — Basics chapter. When used inside a method of an object obj, this resolves to that object obj (given that the method is called on the obj).

Intuitively and logically, the case is quite similar for event handler functions.

That is, when JavaScript calls an event handler, it sets its this value to the object on which the event handler was set up, initially.

For instance, consider the following code:

var buttonElement = document.querySelector('button');

buttonElement.onclick = function() {
   console.log(this === buttonElement);
}

Normally, we'd expect the this inside onclick() to resolve to the buttonElement object, as onclick() is a method of buttonElement. Fortunately, this is the actually the case.

The console's log confirms this:

true

The value true means that this points to the same object inside onclick() above that buttonElement points to.

The case is the exact same for event handlers set up using addEventListener().

For example, if we were to rewrite the code above as follows:

var buttonElement = document.querySelector('button');

buttonElement.addEventListener('click', function() {
   console.log(this === buttonElement);
});

the this inside the handler function above would still resolve to the buttonElement object on which the handler was set up (i.e. the object on which addEventListener() was called).

Let's now consider another example.

In the code below, we have two <button> elements, each with a click handler set up, but to the exact same function, clickHandler():

<button>Button 1</button>
<button>Button 2</button>
var buttonElements = document.querySelectorAll('button');

function clickHandler() {
   alert(this.innerHTML);
}

buttonElements[0].onclick = clickHandler;
buttonElements[1].onclick = clickHandler;

When clickHandler() gets invoked, it alerts the innerHTML of its this object, which might either be the first <button> or the second one.

Live Example

See how with the help of this, we are able to reuse the same click handler on multiple targets.

It's worthwhile mentioning here that often times it might be much more convenient to bind the this of an event handler to some other object instead of the one on which it was set up. Such a use case typically arises when building OOP applications in JavaScript.

In this case, as you may already know, we can use the power-packed bind() function method to create a bound function configured with a given this, and then use this bound function as the event's handler.

Shown below is an example:

class AlertButton {
   constructor(alertMessage) {
      this.message = alertMessage;
      this.clickHandler = this.clickHandler.bind(this);
      this.init();
   }

   init() {
      var buttonElement = document.createElement('button');
      buttonElement.innerHTML = 'Alert button';
      buttonElement.onclick = this.clickHandler;
      document.body.appendChild(buttonElement);
   }

   clickHandler() {
      alert(this.message);
   }
}

new AlertButton('This is the message.');

Live Example

When a new AlertButton is instantiated by providing a message argument to it, the instance saves the message in its message property and then later on uses it when making an alert upon its <button>'s click.

By binding the clickHandler() method's this with the current AlertButton instance, we make sure that when clickHandler() is called, the expression this.message resolves correctly, using the message property of the AlertButton instance.

Otherwise, as we already know, this would resolve to the <button> element on which the click occured, and thus have no message property on it, eventually leading to the wrong alert.

HTML event-handling attributes

Apart from event-handling attributes, and the EventTarget interface, there is yet another way to set up event handlers in JavaScript. That is, by using HTML on- attributes.

The naming of these attributes is exactly the same as the naming of event-handling properties in JavaScript. That is, they also begin with the word on followed the name of the event, for e.g. onclick.

The idea of same naming makes perfect sense — the properties and the HTML attributes are both applied on elements, although the former are specifically applied on element nodes in JavaScript whereas the latter are specifically applied on elements in the HTML source code.

An important thing to keep in mind, however, is that the value of an HTML event-handling attribute is NOT meant to be a function. Rather, it's meant to be a short script defining the code to be executed upon that event's occurence.

In other words,

The value of an HTML event-handling attribute is meant to be the code that would otherwise be put inside the handler function for that event.

Let's see what this means.

Suppose we want to make an alert when the following <button> is clicked.

<button>Click me</button>

We'd write the following if we were to go with the event-handler property approach:

var buttonElement = document.querySelector('button');

buttonElement.onclick = function() {
   alert('Button clicked.');
}

But going with the event-handling HTML attribute approach, we'd write the following:

<button onclick="alert('Button clicked')">Click me</button>

Notice the value of onclick here.

  • Firstly, it's an HTML attribute and therefore we must enclose it within quotes ("").
  • Secondly, it's meant to be a short script handling the event, and therefore we directly call the alert() function in there.

There is no point of defining a function inside an event-handling HTML attribute. In fact, if we did so, the event's occurence won't get the function to be executed, just defined!

The following code won't work as we'd expect it to:

<!--Clicking this button won't make an alert-->
<button onclick="function() { alert('Button clicked') }">Click me</button>

Make sure to keep this in mind.

Although, these HTML attributes can sometimes help us quickly set up event handlers for a given element, their usage is NOT recommended at all. It's always better to keep HTML, i.e. the content, separate from the JavaScript, i.e. the logic — a practice referred to as separation of concerns.

As we develop complex applications, this fact would speak of itself.

With modern web apps using numerous scripts to build their end UI, each serving a different purpose, it becomes really difficult to manage code in these scripts with the passage of time, let alone being able to pay attention to the short scripts embedded inside HTML on- event-handling attributes.

So following this recommendation, throughout this unit, we'll keep ourselves from using event-handling attributes entirely.

If HTML event-handling attributes are bad, then why are they even provided? Why not just remove them?

Good question.

The thing is that event-handling attributes are a remnant of the legacy DOM era, when JavaScript wasn't really deemed by anyone to eventually turn into a humongous technology powering extremely complex frontends. It made sense back then when only simple actions were carried out by the (mostly novice) users of JavaScript.

As far as the question is concerned as to why aren't they removed from JavaScript, this ain't that difficult to reason about.

A lot of legacy code still relies on these somewhat-troublesome attributes. Likewise, if they were to be removed from JavaScript, these legacy apps would cease to work correctly.

In short, HTML event-handling attributes are retained to date in JavaScript simply for backwards-compatibility reasons.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage