Делегирование событий
Обработчик, назначенный через $('li').on(...), достаётся только тем пунктам, которые уже есть на странице. Новые пункты, добавленные позже, его не получат. Решение — делегирование: обработчик ставят на общего родителя, а jQuery проверяет, на каком элементе внутри него произошло событие.
Проблема
<ul id="tasks">
<li>Купить хлеб</li>
</ul>
<button id="add">Добавить задачу</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('li').on('click', function () {
console.log('Щелчок по', $(this).text());
});
$('#add').on('click', function () {
$('#tasks').append($('<li>').text('Новая задача'));
});
</script>Добавьте задачу и щёлкните по ней: в выводе ничего не появится. Обработчик получил только «Купить хлеб».
Решение: селектор вторым аргументом
<ul id="tasks">
<li>Купить хлеб</li>
</ul>
<button id="add">Добавить задачу</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('#tasks').on('click', 'li', function () {
console.log('Щелчок по', $(this).text());
});
$('#add').on('click', function () {
$('#tasks').append($('<li>').text('Новая задача'));
});
</script>Обработчик висит на списке #tasks, а селектор 'li' говорит, на какие элементы внутри реагировать. Теперь щелчок по любому пункту, старому или новому, выводит его текст. this указывает на пункт, а не на список.
Список дел с удалением
Делегирование особенно нужно для кнопок внутри пунктов. Вот почти готовый список дел:
<style>
.done { text-decoration: line-through; color: gray; }
</style>
<input id="title" placeholder="Новая задача">
<button id="add">Добавить</button>
<ul id="tasks"></ul>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
const $list = $('#tasks');
$('#add').on('click', function () {
const $item = $('<li>');
$item.append($('<span>', { class: 'title', text: $('#title').val() }));
$item.append(' ', $('<button>', { class: 'delete', text: 'Удалить' }));
$list.append($item);
});
$list.on('click', '.title', function () {
$(this).toggleClass('done');
});
$list.on('click', '.delete', function () {
$(this).closest('li').remove();
});
</script>Введите задачу и нажмите «Добавить». Щелчок по названию зачёркивает его, кнопка «Удалить» убирает пункт целиком. Метод val() читает поле ввода — о нём подробно в уроке о формах.
Куда ставить обработчик
Родитель для делегирования должен существовать в момент назначения и не пересоздаваться. Обычно это контейнер списка, как #tasks. Можно поставить обработчик и на $(document), но тогда он проверяет каждый щелчок на странице, поэтому лучше выбирать ближайший постоянный элемент.
То же на обычном JavaScript
// script.js
document.querySelector('#tasks').addEventListener('click', (event) => {
const button = event.target.closest('.delete');
if (button) {
button.closest('li').remove();
}
});Без jQuery проверку closest() приходится писать самому — об этом урок учебника о делегировании событий.
Частая ошибка
Назначать обработчик внутри функции, которая добавляет пункт: $item.find('.delete').on('click', ...) при каждом добавлении. Это работает, но обработчиков становится столько же, сколько пунктов, а если по ошибке написать $('.delete').on(...), старые кнопки получат обработчик повторно и сработают по два-три раза. Один делегированный обработчик на списке решает задачу целиком.