HOLIDAY SALE! Save 50% on Membership with code HOLIDAY50. Save 15% on Mentorship with code HOLIDAY15.

7) The Document Object Model Lesson

Introduction to JavaScript Events and Listeners

29 min to complete · By Ian Currie

You want your webpage to be dynamic. You want information to be fetched at the press of a button. Perhaps you want a message to pop up after the user is idle for 5 seconds. Or the text to change color as the user scrolls down. These are all examples of actions you take that are triggered by events. Events drive the interactivity of your webpage. In this lesson, you will learn how to use JavaScript to respond to events.

Asynchronous JavaScript

A key aspect of JavaScript's event handling is the unpredictability of when an event will happen. This unpredictability is essential to grasping the design and functioning of JavaScript. It stems from the asynchronous environment in which JavaScript code operates, where the exact timing of events isn't always known.

Colorful illustration of a light bulb

Asynchronous operations in JavaScript are like sending a letter and then going about your day. You don't wait by the mailbox for a response; instead, you carry on with other work, giving you the chance to perform tasks without blocking the execution.

Eventually, the reply comes, and you deal with it then. This is similar to how some computer programs work. They send out a request – like asking for data from the internet – and then they don't just stop and wait for the answer. They keep doing other tasks, and when the response arrives, they handle it.

This doesn't mean that JavaScript is capable of doing two things at the same time. It's not. It's single-threaded. It just means that JavaScript makes use of queues and event loops to handle asynchronous operations.

JavaScript's design inherently embraces asynchronicity. By leveraging callbacks, promises, and async/await patterns, JavaScript adeptly handles these asynchronous operations, ensuring that web applications can smoothly manage tasks like data requests, processing user interactions, and updating the UI without any perceptible delay to the user.

If you're following along in this beginners course, you'll be covering promises and async/await in future lessons.

Step One: Listen to Events

When you want to respond to an event, you need to first listen for it. You do this by adding an event listener to the element you want to listen. It's like giving an element the ability to hear a certain event. For example, you can give a button the ability to hear a click event.

You can do this with the .addEventListener() method. This method is called on an element, and it takes two arguments:

  1. The type of event you want to listen for, this is a case-sensitive string corresponding to the name of an event. There are many events, listed on MDN's event listing, but here are some of the common one's you'll see often:
    • 'click': When the user clicks on an element
    • 'mouseover': When the user moves the mouse over an element
    • 'mouseout': When the user moves the mouse off of an element
    • 'scroll': When the user scrolls
    • 'keydown': When the user presses a key on the keyboard
    • 'keyup': When the user releases a key on the keyboard
    • 'load': When the page loads
    • 'change': When the user changes the value of an element
    • 'input': When the user inputs text into an element
  2. A callback function that will be run when the event happens.

Here is an example of adding an event listener to a button, assuming that you already have some HTML set up with a button that has an id of "main-button":

let btn = document.getElementById("main-button");

btn.addEventListener("click", () => console.log("The button was clicked!"));

Event listeners take callbacks which are just functions. What makes them callbacks is the fact that they are passed as an argument to another function to use somewhere else or at another time. When the event happens, the callback function is run.

If you run this in the browser, you'll see that every time you click the button, you'll get something logged to the console.

The Event Handler Callback

The callback function that you pass to the .addEventListener() method is called the event handler. It's the function that will be run when the event happens. You can define the handler as a regular function and then pass it in by reference:

let btn = document.getElementById("main-button");

function handleClick() {
  console.log("The button was clicked!");
}

btn.addEventListener("click", handleClick);

Or you can define it as a function expression or an arrow function expression and pass it in as an anonymous function:

btn.addEventListener("click", () => console.log("The button was clicked!"));

You can use regular named functions for handlers if you want to reuse them in other places. But if you only need the handler for one event, it's common to use an anonymous function. If you do name your handler functions, it's typical to include handle in the name. For example, handleClick or handleExitClick.

Another aspect of the event handler is that when it's called by the event listener, it's passed an event object as an argument.

The Event Object

The event object is an object that contains information about the event that just happened. It's passed as an argument to the event handler. All you need to do to use it is create a parameter in your function definition. You can name the parameter it whatever you want, but it's common to name it event or e:

let btn = document.getElementById("main-button");

btn.addEventListener("click", (event) => console.log(event));

If you run this in the browser, you'll see that every time you click the button, you'll get an object logged to the console. This object contains a lot of information about the event that just happened. In the next code block, you'll see an abbreviated example of what the event object looks like:

PointerEvent {
  pointerType: "mouse", // The type of pointer that triggered the event
  altKey: false, // Whether the alt key was pressed
  clientX: 40, // The x coordinate of the pointer relative to the viewport
  clientY: 23, // The y coordinate of the pointer relative to the viewport
  ctrlKey: false, // Whether the ctrl key was pressed
  offsetX: 31, // The x coordinate of the pointer relative to the target element
  offsetY: 14, // The y coordinate of the pointer relative to the target element
  shiftKey: false, // Whether the shift key was pressed
  target: HTMLButtonElement, // The element that the event was triggered on
  timeStamp: 5557.699999988079, // The time at which the event was triggered
  type: "click", // The type of event that was triggered
}

There's much more information in the event object, but you can see here a small selection of the kind of information that is provided with every event. It's a lot!

Colorful illustration of a light bulb

Note: Bear in mind that different event types have different object shapes. So not all event types will result in the same event object with the same keys.

One of the very useful pieces of information that the event object provides is the target property. This is the element that the event was triggered on. In the example above, you can see that the target is the button element itself. This saves you from having to create a closure to access the element that the event was triggered on.

let btn = document.getElementById("main-button");

// Logging the original button element with a closure
btn.addEventListener("click", () => console.log(btn));

// Using the event object to log the button element
btn.addEventListener("click", (event) => console.log(event.target));

This makes it very useful for a concept called event delegation. You'll learn more about event delegation in the next section.

Event Delegation

The event object is also useful for event delegation. Event delegation is when you add an event listener to a parent element and then use the event object to access the element that the event was triggered on.

This makes it very useful for event handlers that are used on multiple elements. For example, if you have a list of buttons, you can use the event object to access the button that was clicked:

<ul id="button-array">
  <li><button>Button 1</button></li>
  <li><button>Button 2</button></li>
  <li><button>Button 3</button></li>
</ul>
let buttons = document.querySelector("#button-array");

buttons.addEventListener("click", (event) => {
  console.log(event.target);
});

If you run this in the browser, you'll see that every time you click on one of the buttons, you'll get the button element that was clicked logged to the console. You didn't have to create references to all the individual buttons and create a closure to access them. You can just use the event object to access the element that was clicked.

The way that events travel up and down the element tree is called event propagation. You'll learn more about event propagation in future lessons.

The this Keyword in Event Handlers

If you use function expressions or regular functions, you can also use the this keyword to access the element that the event was triggered on. This is because the value of this inside an event handler is the element that the event was triggered on:

let btn = document.getElementById("main-button");

btn.addEventListener("click", function () {
  console.log(this);
});

When you run this in the browser, you'll see that every time you click the button, you'll get the button element logged to the console. This is because the value of this inside the event handler is the element that the event was triggered on.

Arrow functions, however, don't have their own this keyword. Instead, they use the this keyword of the surrounding context. In this case, the surrounding context is the global window object. So if you use an arrow function as an event handler, the value of this will be the window object.

Colorful illustration of a light bulb

Beware that this will not always be equivalent to the event.target. If you're using event delegation, for example, this will be the parent element that the event listener is attached to. Or, if you've explicitly set this with the .bind() function, then this will be whatever you've set it to.

So, if you need to access the element that the event was triggered on, it's best to use the event.target property.

Preventing Default Behavior

When you click on a link, the browser will navigate to the URL specified in the href attribute. When you submit a form, the browser will send the data to the URL specified in the action attribute. This is the default behavior of these elements. But sometimes you don't want this default behavior to happen. For example, you might want to validate the form data before it's submitted. Or you might want to do something else when a link is clicked.

You can prevent the default behavior of an event by calling the .preventDefault() method on the event object. This will stop the default behavior from happening. Here is an example of preventing the default behavior of a link:

<a href="https://example.com" id="example-link">Example</a>
let link = document.getElementById("example-link");

link.addEventListener("click", (e) => {
  e.preventDefault();
});

Now, if you click this button in the browser, you'll note that nothing happens. The default behavior of the link has been prevented.

Preventing default behavior is especially used with forms. Typically, you want to validate the form data before it's submitted. You may also not want the page to reload when the form is submitted, as it does by default.

Removing Event Listeners

It's considered best practice, if we no longer need an event listener, to remove it. This is to prevent memory leaks. If you're following along this beginner's course to JavaScript, then you'll likely not run into this situation, but if you do, it's a good habit to get into.

There are two ways to remove event listeners. You can use the .removeEventListener() method, or you can use the AbortController interface.

Using .removeEventListener()

You can remove event listeners by using the .removeEventListener() method. This method takes two arguments:

  1. The type of event to be removed.
  2. The function that was used as the event handler.

Here is an example of removing an event listener:

let btn = document.getElementById("main-button");

function handleClick() {
  console.log("The button was clicked!");
}

btn.addEventListener("click", handleClick);

// Sometime later
btn.removeEventListener("click", handleClick);

As you can see, you need to retain a reference to the handler function to be able to use this method.

Using the Abort Controller

You can also remove event listeners by using the AbortController interface. This is a newer way of removing event listeners and it's a good way to dynamically remove event listeners.

let btn = document.getElementById("main-button");

let controller = new AbortController();
let signal = controller.signal;
let options = { signal: signal };

btn.addEventListener("click", () => console.log("Button clicked"), options);

// Sometime later
controller.abort();

This method involves creating an AbortController object and then passing its signal property to the .addEventListener() method.

The .addEventListener() method takes a third optional argument which is an object with a bunch of optional keys and values. One of these optional keys is "signal". So, you create an object with a "signal" key whose value is the signal property of the AbortController object.

With all that groundwork set, you can call .addEventListener() with the options object as the third argument.

Finally, when you want to remove the event listener, you can call the .abort() method on the controller object.

Here is a more concise version of that same code:

let btn = document.getElementById("main-button");

let controller = new AbortController();

btn.addEventListener("click", () => console.log("Button clicked"), {
  signal: controller.signal,
});

// Sometime later
controller.abort();
Illustration of a lighthouse

Adding event listeners directly in HTML attributes, like using the onclick attribute, is often considered a bad practice due to issues with maintainability, scalability, and separation of concerns. It's recommended to attach event listeners using JavaScript, with methods like .addEventListener(), to adhere to modern web development best practices.

You can also set event listeners directly on HTML elements. This is done by adding an on[event] attribute to the element like onclick. It's value is the code that you want to run when the event happens. For example, you can add an onclick attribute to a button like this:

<button onclick="console.log('The button was clicked!')">Click Me!</button>

You can also use this to call a predefined function:

<button onclick="handleClick()">Click Me!</button>

Which is handy if there's a lot of code involved, however, you can put an arbitrary amount of JavaScript as a value to the on[event] attribute. It's common to use a function call, but you can also put multiple statements in there:

<button
  onclick="console.log('The button was clicked!'); alert('Hello World!');"
>
  Click me!
</button>

This is not the most common way of adding event listeners to elements, in fact, it's highly discouraged by many. It's more common to use the .addEventListener() method.

Colorful illustration of a light bulb

You can also use JavaScript to set event listeners on HTML elements directly too. This is done by setting the on[event] property of the element. For example, you can set the onclick property of a button like this:

let btn = document.getElementById("myBtn");

btn.onclick = () => console.log("The button was clicked!");

Again, it's best to use .addEventListener().

The debate between adding inline JavaScript (directly within HTML elements) and separating JavaScript into external scripts is rooted in best practices for web development, particularly concerning maintainability, readability, and separation of concerns. It's far more common to use the .addEventListener() method to add event listeners to elements, and to keep the JavaScript separate from the HTML. This is the approach that will be used in this course.

Accessibility

When you're adding event listeners to your elements, it's important to consider accessibility.

Colorful illustration of a light bulb

Accessibility is the practice of making your webpages usable by as many people as possible. This includes people with disabilities, people who use assistive technologies such as screen readers, or people who can't use a mouse but instead rely on the keyboard or other input device.

It also doesn't have to mean people with disabilities. You might be browsing on your mobile, for instance, or you may prefer to navigate through a page with the tab and arrow keys.

For example, be wary when overriding the tab key, for example, as that's widely "reserved" for keyboard navigation. If you override it, you may be making your page inaccessible to people who rely on the tab key to navigate.

Use reference sources, accessibility tools like those in the browser developer tools, and feel free to use LLMs like ChatGPT to ask questions about accessibility, and what you can do to make sure that your website is accessible.

More about accessibility will be covered in the next JavaScript course.

Summary: What Are Javascript Events

You've taken an important step in learning how to make interactive webpages using JavaScript events. In this lesson, you've:

  • Recognized how JavaScript handles unpredictability with asynchronous execution.
  • Found out that to make an element respond to user actions, you need to listen for events by adding an event listener to it.
  • Discovered the callback function within .addEventListener() is the event handler that executes when the specified event occurs.
  • Learned that event handlers receive an event object containing details about the event.
  • Analyzed how to use .preventDefault() to stop the default action of an element, which is particularly useful with forms and links.
  • Realized the importance of accessibility, ensuring that your webpages are usable by all people, including those with disabilities or using various assistive technologies.