Event delegation
A handler attached with $('li').on(...) goes only to the items already on the page. Items added later do not get it. The solution is delegation: the handler is placed on a common parent, and jQuery checks which element inside it the event happened on.
The problem
<ul id="tasks">
<li>Buy bread</li>
</ul>
<button id="add">Add a task</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('li').on('click', function () {
console.log('Clicked', $(this).text());
});
$('#add').on('click', function () {
$('#tasks').append($('<li>').text('New task'));
});
</script>Add a task and click it: nothing appears in the output. Only "Buy bread" got the handler.
The fix: a selector as the second argument
<ul id="tasks">
<li>Buy bread</li>
</ul>
<button id="add">Add a task</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$('#tasks').on('click', 'li', function () {
console.log('Clicked', $(this).text());
});
$('#add').on('click', function () {
$('#tasks').append($('<li>').text('New task'));
});
</script>The handler sits on the #tasks list, and the 'li' selector says which elements inside it to react to. Now clicking any item, old or new, prints its text. this points to the item, not the list.
A to-do list with deleting
Delegation is especially needed for buttons inside items. Here is an almost complete to-do list:
<style>
.done { text-decoration: line-through; color: gray; }
</style>
<input id="title" placeholder="New task">
<button id="add">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: 'Delete' }));
$list.append($item);
});
$list.on('click', '.title', function () {
$(this).toggleClass('done');
});
$list.on('click', '.delete', function () {
$(this).closest('li').remove();
});
</script>Type a task and press "Add". Clicking a title crosses it out, the "Delete" button removes the whole item. The val() method reads the input; the form lessons cover it in detail.
Where to attach the handler
The parent used for delegation must exist when the handler is attached and must not be recreated. Usually it is the list container, like #tasks. You can attach the handler to $(document), but then it checks every click on the page, so prefer the nearest permanent element.
The same in plain JavaScript
// script.js
document.querySelector('#tasks').addEventListener('click', (event) => {
const button = event.target.closest('.delete');
if (button) {
button.closest('li').remove();
}
});Without jQuery you write the closest() check yourself; the tutorial lesson on event delegation covers it.
Common mistake
Attaching a handler inside the function that adds an item: $item.find('.delete').on('click', ...) on every addition. It works, but you end up with as many handlers as items, and if you write $('.delete').on(...) by mistake, old buttons get the handler again and fire two or three times. One delegated handler on the list solves the whole task.