Introduction
A page that only runs once when it loads is not interactive. Events are how a page responds to people: a click, a key press, typing in a box, a form being sent. Your code does not sit waiting in a loop. It tells the browser what to do when something happens, and then gets out of the way.
How it works
el.addEventListener("click", handler) asks the browser to call handler every time that event happens on el. The handler is a callback, run by the event loop you met in the asynchronous lesson.
The handler gets an event object with details: event.target (the element it happened on), event.key for key presses, and methods such as event.preventDefault(), which stops the browser's own action (a form submit reloading the page, a link opening).
| Event | When |
|---|---|
click | a button or element is clicked (or pressed with Enter or Space, for buttons) |
input | the text in a box changes |
change | a select, checkbox or box changed and lost focus |
submit | a form is sent |
keydown | a key is pressed |
Forms. input.value is the current text of a box, and it is always a string. Listen for submit on the form, not click on the button, so that pressing Enter works too.
Bubbling and delegation. An event on an element bubbles up: after the handlers on the clicked task run, the same event reaches its list, then the body, then the document. So one listener on a parent can handle all its children, even ones added later. This is called event delegation. event.target.closest("li") finds the item that was clicked, even if the click landed on something inside it. event.stopPropagation() stops the bubbling.
The traced example below is a tiny event system built from plain objects and arrays, the same idea the browser uses inside: listeners are stored in a list for each event name, and emitting an event calls each one in turn:
// A tiny event system: listeners in a list, called on emit.
const listeners = {};
function on(name, handler) {
if (!listeners[name]) {
listeners[name] = [];
}
listeners[name].push(handler);
}
function emit(name, detail) {
const handlers = listeners[name] ?? [];
for (const handler of handlers) {
handler({ type: name, detail });
}
return handlers.length;
}
let clicks = 0;
on("click", (event) => {
clicks++;
console.log("clicked", event.detail);
});
on("click", () => console.log("total clicks:", clicks));
emit("click", "Add button");
emit("click", "Save button");
console.log(emit("keydown", "Enter"), "listeners for keydown");Syntax
button.addEventListener("click", (event) => {
console.log("clicked", event.target);
});
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log(input.value);
});
Example
Type a task and press Enter or Add, and click a task to mark it done. This uses everything so far: a form, a submit handler, the DOM, and a closure over the count variable.
Line by line
- Four
querySelectorcalls find the parts of the page, andcountholds the number of unfinished tasks. updatewrites the count, and picks the singular or plural word.- The submit handler calls
preventDefault, so that the page does not reload. - It trims the text, and stops if nothing was typed.
- It makes the new
li, updates the count and empties the box. - The click listener sits on the list, not on the items.
closest("li")finds the task that was clicked. toggleflips the class, and the count changes to match.
Expected result
At first the page says "0 tasks left". Adding a task lists it and increases the count, and clicking it crosses it out and decreases the count.
In the real world
Search boxes filter results on each input event, and drag-and-drop is built from mouse events. Delegation is especially useful for long lists and tables, because one listener costs far less than thousands of them.
Common mistakes
- Writing
addEventListener("click", handler()). That calls the handler at once and passes its result. Pass the function itself:handler. - Forgetting
event.preventDefault()in a submit handler, so the page reloads and the work is lost. - Writing
"onclick"as the event name. The name is"click". - Adding a listener to every item instead of one listener on the parent.
Best practices
- Use real buttons and forms, so that keyboards work without extra code.
- Use delegation for lists whose items come and go.
- Keep handlers short, and let them call well-named functions.
- Check what
input.valueholds before you trust it.
Summary
addEventListener connects an event to a function. The event object says what happened. preventDefault cancels the browser's own action. Events bubble, so a single listener on a parent can handle all of its children.
Quick check
Key takeaways
- Pass the function itself to addEventListener, without calling it.
- Use preventDefault in a submit handler to stay on the page.
- One listener on a parent also handles children that are added later.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.