← Back to JavaScript Course | Chapter 4: Modern JS, Async & DOM | Lesson 20 of 26

JS Events Advanced

Think of a manager who doesn't personally greet every single visitor at every door in a building, instead they station one person at the main entrance who reports every visitor, no matter which door they use, back to the same central point. Advanced event handling in JavaScript uses that same idea, event delegation lets one listener on a parent element handle events for many children, even ones added later, instead of attaching a separate listener to every single one individually. These patterns, delegation, custom events, and controlling default behavior, are exactly what power more sophisticated interactive features across a real application like cookiescursor.com.

Event Delegation

Event delegation relies on event bubbling, attaching a single listener to a parent element and using the event object's target property to determine exactly which child element actually triggered it, rather than attaching a separate listener to every child.

Note: Event delegation automatically works for children added to the page later, since the listener lives on the stable parent, not on each individual child.

Warning: event.target refers to the exact element that was interacted with, which might be a child of the element you actually meant to check, closest() often helps here.

Example: Event Delegation

javascript
<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
</ul>
<script>
  document.getElementById("list").addEventListener("click", (event) => {
    console.log("Clicked:", event.target.textContent);
  });
</script>

Custom Events

The CustomEvent constructor lets you create and dispatch your own named events, complete with custom data attached, allowing different parts of an application to communicate using the same familiar event system browsers use natively.

Note: Custom events are a clean way to decouple parts of an application, one piece dispatches an event without needing to know exactly who's listening.

Warning: A custom event must be dispatched on a specific element, and only listeners attached to that same element, or its ancestors if it bubbles, will receive it.

Example: Custom Events

javascript
document.addEventListener("greet", (e) => console.log("Received:", e.detail));
const event = new CustomEvent("greet", { detail: "Hello!" });
document.dispatchEvent(event);

preventDefault()

preventDefault() stops a browser's default built-in action for an event, like following a link's href or submitting a form and reloading the page, letting JavaScript take full control of what happens instead.

Note: preventDefault() is commonly used on form submission when you want to validate or send data with JavaScript instead of a traditional full-page form submission.

Warning: preventDefault() only stops the browser's default action, it does not stop the event from continuing to bubble up to parent elements, that requires stopPropagation.

Example: preventDefault()

javascript
<a href="https://example.com" id="link">Click</a>
<script>
  document.getElementById("link").addEventListener("click", (e) => {
    e.preventDefault();
    console.log("Navigation stopped");
  });
</script>

stopPropagation()

stopPropagation() stops an event from continuing to bubble upward through parent elements after it's been handled, which is useful when a click on a nested element shouldn't also trigger a listener attached to one of its ancestors.

Note: Only reach for stopPropagation() when you specifically need to prevent parent listeners from also reacting, it's not needed for most everyday event handling.

Warning: Overusing stopPropagation() can break event delegation patterns elsewhere on the page that were relying on that same bubbling behavior to function correctly.

Example: stopPropagation()

javascript
<div id="outer">
  <button id="inner">Click</button>
</div>
<script>
  document.getElementById("outer").addEventListener("click", () => console.log("Outer"));
  document.getElementById("inner").addEventListener("click", (e) => {
    e.stopPropagation();
    console.log("Inner only");
  });
</script>

A Quick Reference of Event Types

Beyond click, common event types include keydown and keyup for keyboard input, submit for forms, input for real-time text field changes, and scroll for tracking page or element scrolling, each useful in different interactive situations.

Note: The input event fires on every keystroke in a text field, making it better than change for real-time features like live character counters.

Warning: The scroll event can fire extremely frequently during scrolling, attaching heavy logic directly to it without care can noticeably hurt page performance.

Example: A Quick Reference of Event Types

javascript
document.addEventListener("keydown", (e) => console.log("keydown:", e.key));
document.addEventListener("scroll", () => console.log("scroll"));
// Also common: keyup, submit, input
Common Mistakes
  1. Attaching individual event listeners to many dynamically created elements instead of using delegation, which wastes memory and misses future elements.
  2. Forgetting preventDefault() on a form submission when you specifically want to handle it entirely with JavaScript instead of a real page reload.
  3. Calling stopPropagation() without understanding it also prevents other legitimate listeners further up the DOM tree from ever running.
Chapter Summary
  • Event delegation attaches one listener to a parent element, using event.target to identify which specific child was actually interacted with.
  • Custom events, created with the CustomEvent constructor, let different parts of your code communicate through the same event system as native browser events.
  • preventDefault() stops a browser's default action, and stopPropagation() prevents an event from continuing to bubble up to parent elements.
Browser Support

Event delegation, custom events, preventDefault, and stopPropagation are all standard DOM features supported identically in every modern browser.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.