JavaScript

DOM 1.2: Events & Live Interaction — event flow, bubbling, capturing, event delegation, FormData, Observers, layout reflow, and performance debouncing.

1. What is an Event?

An Event is a signal sent by the browser notifying your code that something just occurred — such as a button click, a keyboard stroke, scrolling the viewport, or a form submission. JavaScript listens for these events and executes handler functions in response.

💡 The Doorbell & Notification Bell Analogy

Think of an event listener like a smart doorbell:

  • The Event (Visitor rings the bell): A user clicks a button or types into an input.
  • The Listener (addEventListener): The sensor installed by the door tuned to the bell signal.
  • The Handler Function: The chime sound and notification sent to your smartphone telling you who is at the door.

2. Three Ways to Attach Events

// ❌ 1. Inline HTML attribute (Violates separation of concerns - avoid!)
// <button onclick="handleClick()">Click</button>

// ⚠️ 2. DOM Property Handler (Only supports ONE handler; overwrites previous ones)
btn.onclick = () => console.log("Handler A");
btn.onclick = () => console.log("Handler B"); // Replaces and erases Handler A!

// ✅ 3. addEventListener (Modern standard: supports multiple independent listeners & options)
btn.addEventListener("click", () => console.log("Handler A"));
btn.addEventListener("click", () => console.log("Handler B")); // Both run seamlessly!

Removing Event Listeners

To remove a listener with removeEventListener(), you must pass the exact named function reference. Anonymous functions cannot be removed because they have different references in memory:

function onBtnClick() { console.log("Clicked!"); }

// ✅ Adding and removing with named function:
btn.addEventListener("click", onBtnClick);
btn.removeEventListener("click", onBtnClick);

// 🚀 Modern Cleanup with AbortController (Cancels multiple listeners at once!):
const controller = new AbortController();
window.addEventListener("resize", () => console.log("Resized"), { signal: controller.signal });
window.addEventListener("scroll", () => console.log("Scrolled"), { signal: controller.signal });

controller.abort(); // Instantly removes both listeners simultaneously!

3. The Event Object & Targets

Every event listener automatically receives an Event object (e) containing metadata about what occurred:

btn.addEventListener("click", (e) => {
    console.log("Event Type:", e.type);               // "click"
    console.log("Target (Clicked element):", e.target);
    console.log("CurrentTarget (Attached element):", e.currentTarget);
    console.log("Coordinates:", e.clientX, e.clientY);
    e.preventDefault();   // Stops browser's default action (e.g. form reload, link jump)
    e.stopPropagation();  // Stops event from traveling up the ancestor tree
});
01

e.target (The Originator)

The exact, deepest element that received the raw click or trigger (e.g., the <span> inside a button).

02

e.currentTarget (The Owner)

The element to which the addEventListener is currently attached (e.g., the parent <button> or <ul>).

4. Event Flow: Capturing vs Bubbling

When an element is clicked inside nested containers, the event does not just fire on that single node. It travels in three sequential phases:

1. CAPTURE PHASE:  window ➔ document ➔ body ➔ container ➔ (travels DOWN to target)
2. TARGET PHASE:   button (the actual clicked node)
3. BUBBLE PHASE:   button ➔ container ➔ body ➔ document ➔ window (bubbles back UP)

By default, all event listeners run during the Bubbling phase. To listen in the Capture phase, pass { capture: true }.

▶ Live Lab 1: Event Flow & Bubbling Hierarchy
Interactive Sandbox
JavaScript Event Listener Ctrl+Enter
Document Node
Parent <div class="container">

5. Event Delegation (The Essential Performance Pattern)

The Problem: If you have a table or list with 1,000 items, attaching 1,000 separate event listeners consumes excessive memory and fails for items added dynamically in the future.

The Solution (Event Delegation): Attach ONE single listener to the parent container, and use e.target.closest("selector") to detect which child was clicked as the event bubbles up:

const list = document.createElement("ul");
["Learn JavaScript", "Master DOM", "Build React App"].forEach(text => {
    const li = document.createElement("li");
    li.textContent = text;
    list.append(li);
});
document.body.append(list);

// ✅ ONE delegated listener manages ALL current and future list items:
list.addEventListener("click", (e) => {
    const item = e.target.closest("li");
    if (!item || !list.contains(item)) return; // Ignore clicks outside valid items

    item.classList.toggle("completed");
    console.log("Toggled task:", item.textContent);
});

// Dynamically added item works automatically without adding new listeners!
const newLi = document.createElement("li");
newLi.textContent = "Deploy to Production";
list.append(newLi);
newLi.click(); // Triggers the parent's delegated handler!
▶ Live Lab 2: Event Delegation on Parent <ul>
Interactive Sandbox
JavaScript Editor Ctrl+Enter
  • Master DOM Selection
  • Learn Event Bubbling
  • Prepare for React State

6. Modern Forms & Keyboard Events

Handling Forms with FormData

const form = document.createElement("form");
form.innerHTML = `
    <input name="email" value="alex@dev.io">
    <input name="role" value="Developer">
    <button type="submit">Submit</button>
`;

form.addEventListener("submit", (e) => {
    e.preventDefault(); // Stop default browser page reload!
    
    // Extract all form values cleanly as a plain JS object:
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);
    console.log("Submitted payload:", data); // { email: "alex@dev.io", role: "Developer" }
});
form.requestSubmit();

Keyboard Shortcuts (key vs code)

document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") {
        console.log("Escape pressed: Closing modal");
    }
    if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
        e.preventDefault(); // Stop browser's default Save HTML dialog
        console.log("Custom save triggered!");
    }
});

7. Modern DOM Observers & Performance

Observer API Monitors Real-World Placement Use Case
IntersectionObserver Element visibility in the viewport Lazy-loading images, infinite scrolling feeds, reveal animations
MutationObserver DOM tree modifications (nodes added/removed, attributes) Third-party widgets, chat window mutations, state trackers
ResizeObserver Element dimensions and border-box sizing Responsive charts, dynamic data grids, container queries

Debounce vs Throttle

// 1. Debounce: Waits until activity PAUSES for X milliseconds (Search inputs, resize end)
function debounce(fn, delay = 300) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}

// 2. Throttle: Executes at most ONCE per X milliseconds (Scroll tracking, game loops)
function throttle(fn, limit = 200) {
    let inThrottle = false;
    return (...args) => {
        if (!inThrottle) {
            fn(...args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}
▶ Live Lab 3: Debouncing In Action (Typing vs API Calls)
Performance Lab
JavaScript Editor Ctrl+Enter
Raw Keystrokes 0
Debounced (350ms) 0
Placement MCQ Quiz: Events & Live Interaction
Question 1 of 5
target vs currentTargetQ1 of 5
// <button id="btn"><span>Click</span></button>
const btn = document.getElementById("btn");

btn.addEventListener("click", (e) => {
    console.log(e.target.tagName, e.currentTarget.tagName);
});
// User clicks directly on the word 'Click' inside the <span>
What will be logged to the console?
Event Propagation PhasesQ2 of 5
// Which sequence correctly describes standard DOM event propagation?
What is the exact order of the 3 event propagation phases?
preventDefault vs stopPropagationQ3 of 5
form.addEventListener("submit", (e) => {
    // Stop the default browser full-page reload
    e.preventDefault();
});
What does e.preventDefault() do?
removeEventListener TrapQ4 of 5
btn.addEventListener("click", () => console.log("Ping"));
btn.removeEventListener("click", () => console.log("Ping"));

btn.click();
What happens when btn.click() runs?
Modern ObserversQ5 of 5
// Which API is best suited for implementing lazy-loading images and infinite scroll?
Which browser API replaces heavy window scroll listeners for lazy loading?

8. Hands-On Practice: Delegated Todo Manager (Bridge to React)

Build a self-contained Todo manager using **Event Delegation**: create a list <ul> that listens for clicks, toggles the done class on task items, and deletes items when their delete button (data-action="delete") is clicked.