JavaScript Tutorial menu

JavaScript Event Delegation and Advanced Events

This lesson goes deeper into events: how they travel through the page, and how one listener can handle many elements.

Event Bubbling

When you click a button, the click does not stay on the button. It first happens on the button, then on its parent, then on the parent's parent, all the way up to document. This is called bubbling.

Example

html
<!DOCTYPE html>
<html>
<body>

<div id="outer" style="padding:20px;background:#eee">
  Outer box
  <p id="inner" style="padding:10px;background:#f0db4f">
    <button id="btn">Click me</button>
  </p>
</div>
<p id="log"></p>

<script>
const log = document.getElementById("log");
document.getElementById("btn").addEventListener("click", () => log.innerHTML += "button<br>");
document.getElementById("inner").addEventListener("click", () => log.innerHTML += "paragraph<br>");
document.getElementById("outer").addEventListener("click", () => log.innerHTML += "outer div<br>");
</script>

</body>
</html>
Try it Yourself »
Output
button
paragraph
outer div

One click on the button runs all three listeners, from the inside out.

target and currentTarget

Inside a listener, the event object tells you two different things:

PropertyMeaning
event.targetThe element that was actually clicked (the deepest one)
event.currentTargetThe element whose listener is running now

If you click the button in the example above, the outer listener still sees event.target as the button, while event.currentTarget is the outer div.

Event Delegation

Imagine a list of 100 products, each with a "Buy" button. You could add 100 listeners. A smarter way is to add one listener to the list, and check which button was clicked. This is event delegation.

Delegation has two big benefits:

  • Fewer listeners, so less memory and simpler code
  • It works for items added later, because the listener is on the parent, not on each item

Example

html
<!DOCTYPE html>
<html>
<body>

<h3>Ali's Fruit Stall</h3>
<ul id="fruits">
  <li>Mango - Rs 300/kg <button data-name="Mango">Buy</button></li>
  <li>Banana - Rs 180/dozen <button data-name="Banana">Buy</button></li>
</ul>
<button id="addItem">Add Guava</button>
<p id="msg"></p>

<script>
const list = document.getElementById("fruits");

list.addEventListener("click", (event) => {
  const button = event.target.closest("button");
  if (!button) return;   // the click was not on a button
  document.getElementById("msg").textContent = "You bought: " + button.dataset.name;
});

document.getElementById("addItem").addEventListener("click", () => {
  const li = document.createElement("li");
  li.innerHTML = 'Guava - Rs 200/kg <button data-name="Guava">Buy</button>';
  list.append(li);
});
</script>

</body>
</html>
Try it Yourself »

Click "Add Guava", then click its new "Buy" button. It works, even though no listener was added to it.

event.target.closest("button") finds the nearest button around the clicked element (or the element itself). This matters when a button has an icon or a <span> inside it. The data-name attribute is read with button.dataset.name.

Stopping Bubbling

Sometimes you do not want a click to reach the parent. event.stopPropagation() stops the event from going any higher.

Example

javascript
card.addEventListener("click", () => openCard());

deleteButton.addEventListener("click", (event) => {
  event.stopPropagation();   // do not open the card too
  deleteCard();
});

Do not confuse the two: preventDefault() stops the browser's normal action (like following a link), while stopPropagation() stops the event from bubbling to parents.

Listener Options

addEventListener takes a third argument, an options object:

OptionWhat it does
once: trueThe listener runs one time, then removes itself
capture: trueRuns on the way down (before bubbling)
passive: truePromises you will not call preventDefault(), so scrolling stays smooth

Example

html
<!DOCTYPE html>
<html>
<body>

<button id="offer">Claim your free gift</button>
<p id="demo"></p>

<script>
document.getElementById("offer").addEventListener("click", () => {
  document.getElementById("demo").textContent = "Gift claimed! This button only works once.";
}, { once: true });
</script>

</body>
</html>
Try it Yourself »

Custom Events

You can create your own events with new CustomEvent(name, { detail }) and fire them with dispatchEvent(). The detail property carries any data you like.

Example

html
<!DOCTYPE html>
<html>
<body>

<button id="add">Add to cart</button>
<p id="badge">Cart: 0</p>

<script>
let count = 0;

document.addEventListener("cart:added", (event) => {
  count += event.detail.qty;
  document.getElementById("badge").textContent = "Cart: " + count;
});

document.getElementById("add").addEventListener("click", () => {
  document.dispatchEvent(new CustomEvent("cart:added", { detail: { qty: 1 } }));
});
</script>

</body>
</html>
Try it Yourself »

Custom events help different parts of a page talk to each other without knowing about each other.

Exercise

Exercise

You add one click listener to a <ul> and check event.target to see which <li> was clicked. What is this technique called?

Learn JavaScript with a teacher

Join the Web Development course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp