Lesson 308H.13 - Event Handling
Learning Objectives
By the end of this lesson, learners should be able to:
- Add event listeners for events such as
click
. - Explore the event object.
- Explain event bubbling and capturing.
- Use event bubbling to implement event delegation.
- Stop an event from bubbling.
CodeSandbox
This lesson uses CodeSandbox as one of its tools.
If you are unfamiliar with CodeSandbox, or need a refresher, please visit our reference page on CodeSandbox for instructions on:
- Creating an Account
- Making a Sandbox
- Navigating your Sandbox
- Submitting a link to your Sandbox to Canvas
DOM Events
DOM events are the bedrock of interactivity on web pages. DOM events enable us as developers to implement event-driven programming. This programming paradigm is such that much of our code runs in response to events being triggered during run-time.
Lots of events are being generated within the browser. For example, an event (or multiple events) is created when:
- A user moves or clicks the mouse.
- A user presses a key.
- A form is submitted or its inputs modified.
- The page has finished loading or has been resized.
- etc.
Take a look at the MDN documentation on event to see the sheer number of events and event types.
Event Listeners
An event listener is a function, more specifically, a callback function, that is called when an event fires.
Event listeners may also be referred to as event handlers.
There are three different approaches for registering event listeners:
- In the HTML (inline):
<button id="reset-btn" onclick="reset()">
. - Assigning to DOM elements' properties:
resetBtn.onclick = reset;
. - Calling
addEventListener()
on a DOM element.
Using the HTML approach (onclick="reset()"
) is typically frowned upon because it requires that the function be in the global scope. In addition, this, like inline styling, kind of breaks the separation of concerns design principle.
The DOM element approach (resetBtn.onclick = reset;
) is better because the function does not have to be in global scope, however...
The addEventListener
approach is widely considered to be the best practice because it has the flexibility of adding multiple listener functions!
Here is the common syntax for registering an event listener for a given event:
element.addEventListener(<event-name>, <callback>, <use-capture>);
- event-name is the name of the event (string).
- callback is the function we want executed when the event happens. When called by the JS engine, it will be passed an event object as an argument.
- use-capture is a boolean and is optional. It has to do with event phases. We won't need to worry about it here, but if you want to know more read the Event Phases section of this article.
Event Listener Exercise
We're going to create a small application together to familiarize ourselves with event listeners:
- Create a folder named
events-practice
andcd
into it. - Create an
index.html
and use! [tab]
to create the HTML boilerplate. - Make a
js
folder and ajs/main.js
file.
Add a <script>
tag to include main.js
in the <head>
of our index.html
:
<head>
...
<title>Events Practice</title>
<script defer src="js/main.js"></script>
</head>
The defer
attribute ensures the DOM is ready before the script executes.
Putting a temporary alert('js loaded)
into our script file will verify main.js
is being loaded.
After setting up the project, add the following HTML:
<h3>Comments</h3>
<ul>
<li>Testing Events!</li>
</ul>
<input>
<button>Add Comment</button>
- When we click the Add Comment button, we want to create a new comment with the text entered in the input and then clear the input.
- We can add a
click
event listener to pretty much any element - not just buttons. However, buttons are pre-styled to look and act clickable.
We're going to use an anonymous callback function in this first example:
const btn = document.querySelector('button');
btn.addEventListener('click', function(evt) {
// testing!
console.log(evt);
});
- If all goes well, clicking the button should log out the event object.
- Congrats, registering an event listener is that easy!
The Event Object
Examining the event object that was provided as an argument to our event listener reveals lots of useful information about the event!
Of special interest are:
- Several
...X
and...Y
properties that provide where the click occurred. - The
target
property, which holds a reference to the DOM element that triggered (dispatched) the event. - The JS's
this
keyword within the listener function, which will be set to the DOM element thataddEventListener
was called on.
Knowledge Check
❓ What is the name of the method used to attach event listeners to elements?
❓ What is that method's signature (a method's name, the number & type of arguments it takes, and what it returns)?
❓ Name three events that might be triggered in the browser.
Creating a New <li> Element
Continuing upon our example exercise, if we want to add a new comment we're going to need to create a new <li>
element.
Here's how we can do it using the document.createElement
method:
btn.addEventListener('click', function(evt) {
const li = document.createElement('li');
console.log(li)
});
Note: At this point, the element is "in memory" only and is not part of the DOM (yet).
Okay, we have a new <li>
element created and assigned to a variable named li
, but it has no content.
We want to get whatever text the user has typed into the <input>
element.
Using our <input>
element's value
, we can set the textContent
of the new <li>
:
btn.addEventListener('click', function(evt) {
const li = document.createElement('li');
const inp = document.querySelector('input');
li.textContent = inp.value;
});
Now the new <li>
is ready to be added to the DOM!
- Which element do we we want to add the
<li>
to?
There are several ways to add DOM elements to the document using JavaScript, as previously discussed.
A common way to add new elements to another element is by using the appendChild
method like this:
li.textContent = inp.value;
// new code below
document.querySelector('ul').appendChild(li);
});
Note that the new element is appended as the last child.
Test it out - nice! The new comment has been added, but if we want to improve the UX, we have one more task - clear out the <input>
.
One line of code is all it takes - try to solve it yourself!
Event Bubbling
When an event occurs on an element, that event, whether it is listened to on that element or not, bubbles up through the DOM, all the way up to the document
object.

All event listeners registered for the same event, such as click
, will be invoked along the path to the document
element - unless one of those listeners calls the event object's stopPropagation
method, which we'll touch on later.
Why does JS bubble up (propagate) its events? Event delegation.
Event Delegation
Imagine a web app, like a game perhaps, with lots of elements that need to respond to a click. It's possible there could be tens, hundreds, or more of these elements.
That would be a lot of listeners, wouldn't it? Not very efficient at all. Additionally, every time a new element is added, the event listener would also have to be registered!
Event bubbling allows us to implement what's known as event delegation. Event delegation allows us to register a single event listener that can respond to events triggered by any of its descendants. Much more efficient!
Let's register a listener within our example project (this time for kicks we'll use a named function) on the <ul>
that can respond to clicks on any of its <li>
s:
document.querySelector('ul')
.addEventListener('click', handleClick);
function handleClick(evt) {
console.log(evt);
}
Importantly, the event object's target
property is set to the actual element that was clicked! This allows us to test for specific elements, and run conditional logic.
document.querySelector('ul')
.addEventListener('click', handleClick);
function handleClick(evt) {
if (evt.target.id == 'specialListItem') {
// do something special
}
console.log(evt);
}
Not only is event delegation more efficient by it's very design, it's dynamic. As descendants are added, they too will be listened to!
Without event delegation, you would have to register a listener every time a new element is added, such as each <li>
element in our example.
Exercise
Write the code to change the color of the text of a clicked comment in our practice project.
Stopping Event Propagation
All of that said, event bubbling can sometimes cause headache. The Event
interface includes a stopPropagation
method which will prevent an event from bubbling up into any other elements.
When you have elements nested within other elements and many events being listened to, as is often the case in development, you'll want to keep an eye on how your events bubble, and use stopPropagation
when necessary to prevent erroneous event handling.
Let's look at an example of this in action within this CodeSandbox.
-
Click on the button.
- Notice that despite clicking on the button, the click event "bubbled" up and registered on the
div
andbody
elements as well, which may not be intended behavior.
- Notice that despite clicking on the button, the click event "bubbled" up and registered on the
-
Click slightly to the right of the button.
- Notice that here it only registers the
div
andbody
elements being clicked.
- Notice that here it only registers the
- Add a
stopPropagation
call within the event handler. What happens?
Event Capturing
You can also reverse the process of event bubbling, which is called event capturing. When the { capture: true }
option is passed into addEventListener
, the event fires first on the least nested element, and then on successively more nested elements until the target is reached. Event capturing is disabled by default.
Let's explore this CodeSandbox to see the differences between bubbling and capturing. This example is identical to the earlier example, except with { capture: true }
passed to addEventListener
.
- Click around and observe the results.
- See what happens if you add
stopPropagation
.
Most of the time, event bubbling is the more useful tool.
Default Browser Actions
Many events have default browser actions associated with them. For instance, clicking on a link initiates navigation to that link and submitting a form initiates communication with a server. In many cases, this default behavior runs contrary to what we're trying to accomplish. Luckily, the Event
object has a preventDefault()
method that can be used to stop the default behavior from occuring.
The power of preventDefault
cannot be understated, and is limited only by your creativity. Let's take a look at an example provided by the MDN documentation on preventDefault that shows how a form input can be restricted by preventing the default keypress
action.
Visit this CodeSandbox to explore the example as a class.
Removing Event Listeners
It's possible to remove an added event listener; however, only if a named function was used as the callback:
btn.removeEventListener('click', handleClick);
This would remove the 'click' event listener (handleClick
) that was registered on the btn
element like this: btn.addEventListener('click', handleClick);
.
Knowledge Check
❓ What is the argument that JS passes to an event listener when it calls it?
❓ What is the name of the property on the above argument that represents the DOM element that dispatched the event?
❓ Let's say you needed to have an event listener respond to a click
event on the <td>
s within a <table>
- would you have to add event listeners to each <td>
? Support your answer.
Practicing DOM Manipulation (Part Two)
Now that you've got a handle on event handling, complete R-ALAB 308H.13.1 - DOM Manipulation (Part Two), which expands upon the lab activity that you completed earlier by adding elements with events.