JavaScript Events Advanced


Introduction

In this chapter, we’ll go deeper into how JavaScript handles events behind the scenes, including:

  • Event bubbling vs capturing
  • event.target vs event.currentTarget
  • Event delegation
  • Preventing event propagation

Understanding these concepts is essential for building scalable, dynamic UIs — especially in lists, tables, forms, and SPAs.

Event Bubbling (Default Behavior)

By default, events bubble up the DOM tree — starting from the target element and moving to its ancestors.

<div id="parent">
  <button id="child">Click Me</button>
</div>
document.getElementById("child").addEventListener("click", () => {
  console.log("Child clicked");
});
                                  
document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent clicked");
});

Output if you click the button:

Child clicked
Parent clicked

👉 This is because the event “bubbles” from child to parent. Know more about event bubbles.

Stopping Bubbling

Use event.stopPropagation() to prevent the event from bubbling up.

document.getElementById("child").addEventListener("click", function (event) {
  event.stopPropagation();
  console.log("Only child clicked");
});

Now, clicking the button won’t trigger the parent’s listener.

Event Capturing (Trickier)

Capturing is the opposite of bubbling: the event goes top → bottom in the DOM before hitting the target.

Enable capturing with the 3rd parameter of addEventListener():

element.addEventListener("click", handler, true);
document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent Capturing");
}, true);
                                  
document.getElementById("child").addEventListener("click", () => {
  console.log("Child Bubble");
});

Capturing runs first (if enabled), then bubbling.

event.target vs event.currentTarget

  • event.target: The actual element that triggered the event
  • event.currentTarget: The element the handler is attached to
document.getElementById("parent").addEventListener("click", function (e) {
  console.log("Target:", e.target); // What was clicked
  console.log("Current Target:", e.currentTarget); // #parent
});

Event Delegation (Very Useful)

Instead of adding event listeners to multiple child elements, add one listener to their parent and check which child triggered it using event.target.

Great for dynamic content like to-do lists or comment sections.

Example: Click on dynamic list items

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
</ul>
document.getElementById("myList").addEventListener("click", function (e) {
  if (e.target.tagName === "LI") {
    alert("You clicked " + e.target.textContent);
  }
});

This works even if new <li> items are added later — no need to bind again!

once Option (Run a Handler Only Once)

button.addEventListener("click", function () {
  alert("You’ll see me only once!");
}, { once: true });

Combining It All: A Practical Example

<ul id="tasks">
  <li>Task 1 <button>Delete</button></li>
  <li>Task 2 <button>Delete</button></li>
</ul>
document.getElementById("tasks").addEventListener("click", function (e) {
  if (e.target.tagName === "BUTTON") {
    e.target.parentElement.remove(); // Remove <li>
  }
});

This is cleaner and more efficient than assigning listeners to every button individually.

🧪 Practice Exercise:

Task:

  1. Demonstrate event bubbling with nested elements.
  2. Prevent bubbling with stopPropagation.
  3. Use event delegation on a list where clicking an item changes its color.
  4. Create a "run once" button using { once: true }.
  5. Log target and currentTarget and observe the difference.