One Handler for a Whole List

A list can have many items: ten, a hundred, or new ones that your script adds later. Giving each item a handler of its own would be a lot of work. There's an easier way: one handler for the whole list.

It works because events bubble up. A click on an item is also a click on everything the item is in: the list, the body and the whole page. So the list's click handler runs whenever someone clicks any of its items, and event.target says which one it was:

<ul id="animals">
    <li>Cat</li>
    <li>Dog</li>
</ul>
const animalList = document.querySelector("#animals");

animalList.addEventListener("click", function (event) {
    console.log(event.target.textContent);
});

A click on Dog prints Dog. The handler is on the list, but event.target is the item that was clicked. And it works for items added to the list later too, without any new handler.

There's a catch: event.target is the innermost element that was clicked. When an item has other elements inside it, such as <li><strong>Cat</strong>, 3 years old</li>, a click on the word Cat gives you the strong, not the li. And a click next to the items, on the list itself, gives you the ul.

The method closest() solves this. You give it a selector, as you give one to querySelector(). It starts at the element itself and goes up through the elements it is in, until it finds one that matches:

animalList.addEventListener("click", function (event) {
    const item = event.target.closest("li");
    if (item === null) {
        return;
    }
    console.log(item.textContent);
});

Wherever in the item you click, item is the li. When the click wasn't on an item, closest() finds nothing and returns null, and the handler ends with return. Without this check, item.textContent would end with the error TypeError: Cannot read properties of null (reading 'textContent').

The items usually carry their data in data attributes, such as <li data-age="3">, and the handler reads them with item.dataset.age.

Instructions

Give recipeList one click handler. In it, find the clicked recipe with event.target.closest("li"), and end the handler when there's none. Otherwise, show the time from the recipe's data-minutes in timeText: Cooking time: 15 minutes for the pancakes. Try clicking the name of a recipe and the rest of its line.

Highlight the recipe that was clicked: give its li the class chosen. Only one recipe is highlighted at a time, so keep the highlighted item in a variable (let chosenItem = null;, outside the handler). Before you highlight a new one, remove the class from the previous one if there is one (chosenItem isn't null), and then save the new one in the variable.

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

Back

3/10

Next