- Home
- /
- Tutorials
- /
- JavaScript Tutorial
- /
- JavaScript Event Delegation
JavaScript Events
JavaScript Event Delegation
Event delegation puts one listener on a parent instead of many on its children.
It works because events bubble up from the element that was clicked.
The big win is that it also covers elements added later.
Example
Example
javascript
document.body.innerHTML =
'<ul id="list"><li>A</li><li>B</li></ul>';
document.getElementById("list").addEventListener("click", function (event) {
console.log("clicked " + event.target.textContent);
});
document.querySelectorAll("li")[1].click();One listener on the list handles every item.
What is Delegation?
Attach one listener to a container element.
Use event.target to find out which child was actually clicked.
New children are handled automatically, with no extra listeners.
Syntax
Syntax
javascript
parent.addEventListener("click", function (event) {
const item = event.target.closest(".item");
if (!item) return;
});closest handles clicks that land on a nested element.
The Problem It Solves
Without delegation, every item needs its own listener.
A hundred items means a hundred listeners, and new ones get none.
Example
Example
javascript
document.body.innerHTML = '<ul id="list"><li>A</li></ul>';
const list = document.getElementById("list");
let clicks = 0;
list.addEventListener("click", function () {
clicks = clicks + 1;
});
const extra = document.createElement("li");
extra.textContent = "B";
list.append(extra);
extra.click();
console.log(clicks);The new item worked immediately, with no listener of its own.
Checking What Was Clicked
A click on the container itself should usually be ignored.
Example
Example
javascript
document.body.innerHTML =
'<ul id="list"><li class="item">A</li></ul>';
document.getElementById("list").addEventListener("click", function (event) {
if (event.target.classList.contains("item")) {
console.log("item clicked");
} else {
console.log("ignored");
}
});
document.getElementById("list").click();
document.querySelector(".item").click();The output is ignored then item clicked.
Using closest
If an item contains other elements, target may be one of those.
closest walks up until it finds the item itself.
Example
Example
javascript
document.body.innerHTML =
'<ul id="list"><li class="item"><span id="label">A</span></li></ul>';
document.getElementById("list").addEventListener("click", function (event) {
const item = event.target.closest(".item");
console.log(item === null ? "outside" : "found the item");
});
document.getElementById("label").click();The click landed on the span, but the item was still found.
Deciding by Action
A data attribute lets one handler serve several buttons.
Example
Example
javascript
document.body.innerHTML =
'<div id="bar">' +
'<button data-action="save">Save</button>' +
'<button data-action="delete">Delete</button>' +
'</div>';
document.getElementById("bar").addEventListener("click", function (event) {
const action = event.target.dataset.action;
if (action) {
console.log("running " + action);
}
});
document.querySelectorAll("button").forEach(function (b) { b.click(); });The output is running save then running delete.
Removing an Item
Delegation makes a delete button on each row straightforward.
Example
Example
javascript
document.body.innerHTML =
'<ul id="list"><li class="item">A <button class="x">X</button></li>' +
'<li class="item">B <button class="x">X</button></li></ul>';
document.getElementById("list").addEventListener("click", function (event) {
if (event.target.classList.contains("x")) {
event.target.closest(".item").remove();
}
});
document.querySelector(".x").click();
console.log(document.querySelectorAll(".item").length);The output is 1, because the first row was removed.
Complete Example
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Event Delegation</title>
</head>
<body>
<h1>Event Delegation</h1>
<ul id="list">
<li class="item">First</li>
<li class="item">Second</li>
</ul>
<button id="add">Add an item</button>
<p id="out"></p>
<script>
const list = document.getElementById("list");
const out = document.getElementById("out");
list.addEventListener("click", function (event) {
const item = event.target.closest(".item");
if (item) {
out.textContent = "You clicked: " + item.textContent;
}
});
document.getElementById("add").addEventListener("click", function () {
const item = document.createElement("li");
item.className = "item";
item.textContent = "New item";
list.append(item);
});
</script>
</body>
</html>Try It Yourself
Run the above example in the Try It Editor.
Try the new items:
Add a few items and click them; they work without any extra code.
Important Points
- One listener on a parent can handle all its children.
- It works because events bubble.
event.targetsays which child was clicked.closesthandles clicks on nested elements.- Elements added later are handled automatically.
Conclusion
Event delegation is the standard pattern for lists and tables.
It uses fewer listeners and copes with content that changes.
Once you have used it, attaching listeners in a loop feels clumsy.
