Один обработчик на весь список
В списке может быть много пунктов: десять, сто или новые, которые ваш скрипт добавит позже. Давать каждому пункту свой обработчик — слишком много работы. Есть способ проще: один обработчик на весь список.
Он работает потому, что события всплывают. Нажатие на пункт — это одновременно и нажатие на всё, в чём этот пункт лежит: на список, на body и на всю страницу. Поэтому обработчик клика на списке запускается, когда кто-то нажимает на любой из его пунктов, а event.target подсказывает, на какой именно:
<ul id="animals">
<li>Кошка</li>
<li>Собака</li>
</ul>const animalList = document.querySelector("#animals");
animalList.addEventListener("click", function (event) {
console.log(event.target.textContent);
});Нажатие на «Собака» выведет Собака. Обработчик висит на списке, но event.target — это пункт, на который нажали. И это работает и для пунктов, добавленных в список позже, без всяких новых обработчиков.
Есть одна загвоздка: event.target — это самый вложенный элемент, на который нажали. Если внутри пункта лежат другие элементы, например <li><strong>Кошка</strong>, возраст: 3</li>, то нажатие на слово «Кошка» даст вам strong, а не li. А нажатие рядом с пунктами, на самом списке, даст ul.
Эту проблему решает метод closest(). Ему передают селектор, как в querySelector(). Он начинает с самого элемента и поднимается по элементам, в которых тот лежит, пока не найдёт подходящий:
animalList.addEventListener("click", function (event) {
const item = event.target.closest("li");
if (item === null) {
return;
}
console.log(item.textContent);
});Куда бы в пункте вы ни нажали, item — это li. Если нажали не на пункт, closest() ничего не находит и возвращает null, и обработчик заканчивается на return. Без этой проверки item.textContent закончилось бы ошибкой TypeError: Cannot read properties of null (reading 'textContent').
Данные пунктов обычно хранятся в атрибутах data, например <li data-age="3">, а обработчик читает их через item.dataset.age.
Задание
- 1
Дайте
recipeListодин обработчик клика. Найдите в нём рецепт, на который нажали, с помощьюevent.target.closest("li")и завершите обработчик, если такого нет. Иначе покажите вtimeTextвремя из атрибутаdata-minutesэтого рецепта: для блинов этоВремя приготовления: 15 мин.Попробуйте нажать на название рецепта и на остальную часть его строки. - 2
Выделите рецепт, на который нажали: дайте его
liклассchosen. Выделен может быть только один рецепт за раз, поэтому храните выделенный пункт в переменной (let chosenItem = null;вне обработчика). Прежде чем выделить новый пункт, уберите класс у предыдущего, если он есть (chosenItemне равенnull), а затем сохраните новый пункт в переменной.