Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Делегирование событий

Обработчик, назначенный через $('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(...), старые кнопки получат обработчик повторно и сработают по два-три раза. Один делегированный обработчик на списке решает задачу целиком.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.