Events — Bubbling, Delegation & Custom Events

Page-কে interactive করুন — বিশ্বকে আবার build না করেই

~40 min Advanced 14 practice problems Live runner

1. addEventListener

btn.addEventListener("click", e => {
    console.log("clicked", e.target);
});

// Options
btn.addEventListener("click", handler, {
    once:    true,    // run only once, then auto-remove
    capture: false,   // listen during capture phase
    passive: true,    // promise not to call preventDefault (better scroll perf)
});

// Remove — must use the SAME function reference
btn.removeEventListener("click", handler);
এক element-এ একই event-এ একাধিক listener বসানো যায় — কেউ কাউকে overwrite করবে না। Remove করতে হলে identical function reference লাগবে।

2. The Three Phases — Capture, Target, Bubble

document section.parent button.target ← clicked here capture: document → parent → target bubble: target → parent → document Figure 26.1 — by default listeners fire on the bubble phase.

3. Event Delegation

Instead of attaching a listener to every <li>, attach one listener on the parent and use e.target to identify which child was clicked. Performance + dynamic children for free.

delegate.js
// Sandbox simulation of delegation
const bus = {
    listeners: [],
    on(target, fn) { this.listeners.push({ target, fn }); },
    emit(event) {
        // Event bubbles target → parent
        let el = event.target;
        while (el) {
            this.listeners
                .filter(l => l.target === el)
                .forEach(l => l.fn(event));
            el = el.parent;
        }
    }
};

const ul = { tag: "ul", parent: null };
const li = { tag: "li", text: "item 1", parent: ul };

// One listener on the parent — handles all children
bus.on(ul, e => console.log("clicked:", e.target.text));

bus.emit({ type: "click", target: li });

Real DOM equivalent:

document.querySelector("ul").addEventListener("click", e => {
    const li = e.target.closest("li");
    if (!li) return;
    console.log("clicked:", li.textContent);
});

4. preventDefault & stopPropagation

// Prevent navigation on a link click
link.addEventListener("click", e => {
    e.preventDefault();
    customRoute(link.href);
});

// Don't let parent listeners see this event
btn.addEventListener("click", e => {
    e.stopPropagation();
});

// Stop ALL further listeners — including siblings on the same node
e.stopImmediatePropagation();
Don't reach for stopPropagation reflexively It breaks delegation higher up. Prefer to filter inside your handler ("only act if e.target matches X").

5. Common Events

EventFired On
clickButtons, links, anything clickable
inputEvery keystroke in an input/textarea
changeAfter an input loses focus with a new value
submitForms (call e.preventDefault to stop reload)
keydown / keyupKeyboard
mouseover / mouseoutHover
scrollElement scrolled — throttle or use passive: true
DOMContentLoadedHTML parsed, scripts not yet loaded
loadEverything (images, CSS) loaded

6. CustomEvent

custom.js
// Sandbox-safe pattern: an EventTarget
const bus = new EventTarget();

bus.addEventListener("user:login", e =>
    console.log("got user:", e.detail));

bus.dispatchEvent(new CustomEvent("user:login", {
    detail: { id: 42, name: "Arif" }
}));

Real DOM elements are also EventTargets, so the same pattern works on any element.

7. Glossary (শব্দকোষ)

TermMeaningবাংলায়
EventAn object describing a user/system action (click, input, resize).User/system-এর কোনো action-এর object।
addEventListenerAttaches a handler to an element for an event type.Element-এ event-এর জন্য handler যোগ করে।
Capture phaseEvent flowing top-down before reaching the target.Target-এ পৌঁছানোর আগে উপর থেকে নিচে flow।
Bubble phaseEvent flowing back up to ancestors after the target.Target-এর পরে নিচ থেকে উপরে flow।
event.targetThe deepest element that triggered the event.সবচেয়ে গভীর element যেখান থেকে event এসেছে।
event.currentTargetThe element whose listener is currently running.যে element-এর listener এখন চলছে।
DelegationOne listener on a parent handles many children.Parent-এ এক listener — সব children handle করে।
preventDefaultCancels the browser's default action (e.g. link nav).Browser-এর default action আটকায়।
stopPropagationStops the event from reaching ancestors.Ancestor পর্যন্ত event পৌঁছাতে দেয় না।
CustomEventApp-defined event with a detail payload.App-এর নিজস্ব event — detail-এ data।
passiveListener promise: won't call preventDefault; better scroll perf.preventDefault না call করার প্রতিশ্রুতি — scroll-এ দ্রুত।
মনে রাখুন: Event bubble করে; তাই parent-এ একটি listener দিয়ে সব children-এর event handle করা যায় — এটাই delegation। e.target = যেখান থেকে event এসেছে; e.currentTarget = যে listener এখন চলছে। Scroll/touch listener-এ { passive: true } দিন — উল্লেখযোগ্যভাবে smoother।

8. Practice Problems

  1. Sketch a click handler that logs the clicked element.
    ✨ Show Answer
    btn.addEventListener("click", e => console.log(e.target));
  2. Prevent a form's default submit.
    ✨ Show Answer
    form.addEventListener("submit", e => {
        e.preventDefault();
        // ... handle in JS
    });
  3. Use delegation: handle clicks on any <li> inside <ul>.
    ✨ Show Answer
    ul.addEventListener("click", e => {
        const li = e.target.closest("li");
        if (!li || !ul.contains(li)) return;
        console.log("clicked:", li.textContent);
    });
  4. Build an EventTarget bus and emit a custom event with detail.
    ✨ Show Answer
    a4.js
    const bus = new EventTarget();
    bus.addEventListener("hi", e => console.log("got", e.detail));
    bus.dispatchEvent(new CustomEvent("hi", { detail: "hello" }));
  5. Use { once: true } to attach a one-time listener.
    ✨ Show Answer
    btn.addEventListener("click", run, { once: true });
  6. Why is event delegation faster than attaching N listeners?
    ✨ Show Answer

    Answer: One listener replaces N listeners — less memory, less work when the DOM changes. Newly added children are handled automatically because they bubble through the same parent. With per-child listeners, each new element needs its own registration.

  7. Stop a click on a button from reaching the parent (showing the trade-off).
    ✨ Show Answer
    btn.addEventListener("click", e => {
        e.stopPropagation();   // breaks any delegation higher up
    });
  8. Listen for keydown and log the key name.
    ✨ Show Answer
    document.addEventListener("keydown", e => console.log(e.key));
  9. Build a tiny pub/sub object using EventTarget.
    ✨ Show Answer
    a9.js
    const hub = new EventTarget();
    const on  = (n, f) => hub.addEventListener(n, e => f(e.detail));
    const emit = (n, d) => hub.dispatchEvent(new CustomEvent(n, { detail: d }));
    
    on("chat", m => console.log("msg:", m));
    emit("chat", "হ্যালো");
  10. Why is passive: true recommended for scroll handlers?
    ✨ Show Answer

    Answer: The browser knows you won't call preventDefault, so it can start scrolling immediately without waiting for your handler — visibly smoother on touch devices.

  11. Remove a listener after it runs three times.
    ✨ Show Answer
    a11.js
    const hub = new EventTarget();
    let n = 0;
    function handler() {
        console.log("call", ++n);
        if (n >= 3) hub.removeEventListener("x", handler);
    }
    hub.addEventListener("x", handler);
    for (let i = 0; i < 5; i++) hub.dispatchEvent(new Event("x"));
  12. In one paragraph, explain why preventDefault doesn't work on a passive listener.
    ✨ Show Answer

    Answer: A passive listener is a promise to the browser that you won't cancel the default. The browser can therefore optimize by not waiting for your handler to finish before scrolling. If you call preventDefault inside, the browser ignores it and emits a console warning.

  13. Show how e.currentTarget differs from e.target.
    ✨ Show Answer

    Answer: e.target is whatever the user actually interacted with (deepest node). e.currentTarget is the element whose listener is currently running. With delegation, currentTarget is the parent and target is the clicked child.

  14. Build a runnable simulator that bubbles a "click" through three nodes.
    ✨ Show Answer
    a14.js
    const nodes = ["target", "section", "body", "document"];
    nodes.forEach(n => console.log("bubbling through:", n));

Summary — Module 26

Use addEventListener with options for once/passive. Events bubble — exploit it with delegation. preventDefault blocks the browser's default; stopPropagation blocks ancestors. Custom events ride on EventTarget for cross-component messaging.

Event bubble করে — তাই parent-এ একটি listener দিয়ে সব children handle করা যায় (delegation)। passive: true scroll-এ অপরিহার্য।

Next Module → Forms & Validation।