Mastering the Javascript event system (Part 1)

Events are an important part of any interactive web app. Events are used to respond when a user clicks somewhere, focuses on a link with their keyboard, and changes the text in a form. When I first started learning Javascript, I wrote complicated event listeners. More recently I’ve learned how to reduce both the amount of code I write and the number of listeners I need.

Let’s start with a simple example - a navigation element with a few links. We want to print a message to the console showing where a hyperlink goes when the user focuses on it.

<nav>
    <a href="#first">​First link.​</a>
    <a href="#second">Second link.</a>
    <a href="#third">Third link.</a>
</nav>

The intuitive way

When I first started learning about Javascript events, I wrote separate event listener functions for each element. I see this as a common pattern because it’s the simplest way to start - we want specific behavior for each link, so we can use specific code for each.

document.querySelector('a[href="#first"]').addEventListener('focusin', evt => {
    console.log('#first');
});

document.querySelector('a[href="#second"]').addEventListener('focusin', evt => {
    console.log('#second');
});

document.querySelector('a[href="#third"]').addEventListener('focusin', evt => {
    console.log('#third');
});

Reducing duplicate code

The above event listeners are all very similar. Each function prints some text. This duplicate code can be collapsed into a helper function.

function print(text) {
    console.log(text);
}

document
    .querySelector('a[href="#first"]')
    .addEventListener('focusin', evt => print('#first'));
document
    .querySelector('a[href="#second"]')
    .addEventListener('focusin', evt => print('#second'));
document
    .querySelector('a[href="#third"]')
    .addEventListener('focusin', evt => print('#third'));

This is much cleaner, but we still need many functions and event listeners.

Taking advantage of the Event object

The key to simplifying your listeners is the Event object. When an event listener is called, it also sends an Event object as the first argument. This object has some data to describe the event that occurred, such as the time the event happened. To simplify our code, we can use the evt.currentTarget property. currentTarget refers to the element that the event listener is attached to. In our example, it will be one of the 3 links.

const print = evt => {
    const text = evt.currentTarget.href;
    console.log(text);
};

document.querySelector('a[href="#first"]').addEventListener('focusin', print);
document.querySelector('a[href="#second"]').addEventListener('focusin', print);
document.querySelector('a[href="#third"]').addEventListener('focusin', print);

Now there is only 1 function instead of 4. We can re-use the exact same function as an event listener and evt.currentTarget.href will have a different value depending on the element that fired the event.

Using bubbling

One final change can be made to reduce the number of lines in our code. Rather than attaching an event listener to each link, we can just attach a single event listener to the <nav> element that contains all the links.

When an event is fired, it starts off at the element where the event originated (one of the links). However, it won’t stop there. The browser goes to each parent of that link, calling any event listeners on those parents. This will continue until the root of the document is reached (the <body> tag in HTML). This process is called “bubbling”, as the event rises through the document tree like a bubble.

Animation of event bubbling

By attaching an event listener to the list, the focus event will bubble from the link that was focused up to the parent list. We can also take advantage of the evt.target property, which contains the element that fired the event (one of the links) rather than the element that the event listener is attached to (the <nav> element).

const print = evt => {
    const text = evt.target.href;
    console.log(text);
};

document.querySelector('nav').addEventListener('focusin', print);

Now the many event listeners have been collapsed to just one! With more complicated code, the effect will be greater. By utilizing the Event object and bubbling, you can master Javascript events and simplify your event handler code.


In this post I covered working with Event objects and bubbling to use a single event listener to handle related elements. In the next post, I will talk about dealing with click events where many elements may fire.

Next in the series: What about click events?