The Event Object, Bubbling & Delegation
When you click a button inside a list inside a page, who hears the click? Understanding how events travel — and one pro trick called delegation — makes dynamic lists effortless.
What you will learn
- Read useful details from the event object
- Explain event bubbling in plain words
- Use event delegation for dynamic lists
The event object — the details of what happened
Every time an event fires, the browser hands your handler one argument: the event object (we usually call it e). It is a little report describing what happened — which element was involved, what was typed, where the mouse was, and so on. You met e.target briefly in the Events lesson; here we look closer.
The most useful piece is e.target — the exact element that the event happened on. The example below has three buttons but only one handler. When you click any button, e.target tells us which one you actually hit.
<div id="panel">
<button>Red</button>
<button>Green</button>
<button>Blue</button>
</div>
<p id="out">Click a button.</p>
<script>
document.getElementById("panel").addEventListener("click", function (e) {
document.getElementById("out").textContent = "You clicked: " + e.target.textContent;
});
</script>The listener is on the <div id="panel">, not on the buttons. When you click "Green", the click happens on the button, so e.target is that button and e.target.textContent is "Green". The same one handler correctly reports whichever button you press. That only works because of bubbling, which we explain next.
Note: Output: Click Red → You clicked: Red Click Green → You clicked: Green Click Blue → You clicked: Blue
Bubbling — events travel upward
When you click an element, the event does not stop there. It bubbles: it fires on the element you clicked, then on that element’s parent, then its grandparent, all the way up to the page. Think of a bubble rising from the bottom of a glass to the top.
So a click on a button inside a div inside the body fires on the button first, then the div, then the body — in that order. This is why a single listener on a parent can "hear" clicks on all of its children: the click bubbles up to the parent and triggers its handler.
- You click the innermost element (say, a
<button>). - The event fires on that button first — this is where
e.targetpoints. - The same event then fires on the button’s parent, then its parent, rising upward.
- Any listener it passes on the way up runs. It stops at the top of the document.
- You can halt this rise early by calling
e.stopPropagation()inside a handler.
Tip: Two names that sound alike: e.target is the element you actually clicked (deepest). e.currentTarget is the element whose listener is currently running (the parent, in delegation). They are often different — keep them straight.
Event delegation — one listener for a whole list
Event delegation is a professional pattern built directly on bubbling. Instead of attaching a separate listener to every item in a list, you attach one listener to the shared parent and use e.target to figure out which child was clicked.
Why bother? Because items added later automatically work too. A listener attached to each existing item cannot hear clicks on items you create afterwards — but a single parent listener catches every click that bubbles up to it, no matter when the item was added. The demo below adds new rows, and deletion keeps working on all of them.
<button id="add">Add item</button>
<ul id="todos">
<li>First item <span class="x">✕</span></li>
</ul>
<script>
const list = document.getElementById("todos");
// ONE listener on the parent handles every ✕, even on future items
list.addEventListener("click", function (e) {
if (e.target.classList.contains("x")) {
e.target.parentElement.remove(); // remove the whole <li>
}
});
document.getElementById("add").addEventListener("click", function () {
const li = document.createElement("li");
li.innerHTML = "New item <span class='x'>✕</span>";
list.appendChild(li);
});
</script>Reading it step by step:
- We attach one click listener to the
<ul>parent, not to each ✕. - When any ✕ is clicked, the click bubbles up to the
<ul>and runs our handler. e.target.classList.contains("x")checks whether the thing actually clicked was a ✕ (and not, say, the item text).- If it was a ✕,
e.target.parentElementis its<li>, and.remove()deletes that whole row. - Crucially, the Add item button creates brand-new
<li>rows — and their ✕ buttons work immediately, because the single parent listener catches their bubbled clicks too.
Note: Output: Click ✕ on "First item" → that row disappears. Click "Add item" twice → two new rows appear. Click ✕ on a NEW row → it disappears too — no extra code needed.
Watch out: Without delegation you would have to attach a fresh listener to every new ✕ you create — easy to forget, and wasteful for long lists. Delegation is the clean, standard fix, and a very common interview question.
Q. In event delegation, why does a single listener on the parent also handle clicks on items added later?
✍️ Practice
- Build a list where clicking any item logs its text using one parent listener and
e.target. - Add a button that appends new items, and confirm delegation handles their clicks without new listeners.
🏠 Homework
- Refactor your To-Do project to use event delegation for the delete buttons instead of one listener per item.