Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
18 minLesson 13 of 40
JavaScript for Web

Event Handling & Delegation

Event Handling & Delegation

Events connect user actions to your JavaScript code. Understanding event delegation — attaching one listener to a parent instead of many listeners to children — is what makes dynamic UIs performant.

Adding Event Listeners

const btn = document.querySelector("#submit");

btn.addEventListener("click", (event) => {
    event.preventDefault();
    console.log("Clicked!");
});

// Named function (can be removed later)
function handleClick(e) {
    console.log(e.target);
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);

// Options: once, passive, capture
btn.addEventListener("click", handleClick, { once: true });
window.addEventListener("scroll", onScroll, { passive: true });

Event Delegation

Instead of adding listeners to every child element, add ONE to a parent. This works for dynamically added elements too:

// ❌ Adding to every item — breaks when items are added dynamically
document.querySelectorAll(".todo-item").forEach(item => {
    item.addEventListener("click", handleItemClick);
});

// ✅ One listener on the parent
const list = document.querySelector(".todo-list");

list.addEventListener("click", (event) => {
    // Find the closest ancestor matching the selector
    const item = event.target.closest(".todo-item");
    if (!item) return;  // clicked on list but not an item

    const id = item.dataset.id;

    if (event.target.matches(".delete-btn"))  deleteItem(id);
    if (event.target.matches(".complete-btn")) toggleItem(id);
    if (event.target.matches(".edit-btn"))    editItem(id);
});

Common Event Types

// Mouse
element.addEventListener("click", handler);
element.addEventListener("dblclick", handler);
element.addEventListener("mouseenter", handler);  // fires only on element
element.addEventListener("mouseleave", handler);
element.addEventListener("mouseover", handler);   // fires on children too
element.addEventListener("contextmenu", (e) => {
    e.preventDefault(); // custom right-click menu
});

// Keyboard
document.addEventListener("keydown", (e) => {
    if (e.key === "Enter") submit();
    if (e.key === "Escape") closeModal();
    if (e.ctrlKey && e.key === "s") save();
    if (e.key === "ArrowUp") navigateUp();
});

// Form
form.addEventListener("submit", (e) => {
    e.preventDefault();
    processForm(new FormData(form));
});

input.addEventListener("input", (e) => {     // every keystroke
    updatePreview(e.target.value);
});
input.addEventListener("change", handler);    // after blur
input.addEventListener("focus", handler);
input.addEventListener("blur", handler);

// Window
window.addEventListener("resize", debounce(handleResize, 100));
window.addEventListener("scroll", throttle(handleScroll, 16), { passive: true });
document.addEventListener("DOMContentLoaded", init);
window.addEventListener("load", onFullyLoaded);
window.addEventListener("beforeunload", saveState);

Event Object

element.addEventListener("click", (event) => {
    event.target;           // element that triggered the event
    event.currentTarget;    // element the listener is on
    event.type;             // "click"
    event.timeStamp;        // when it happened

    // Mouse
    event.clientX;          // viewport coordinates
    event.pageX;            // page coordinates (includes scroll)
    event.button;           // 0=left, 1=middle, 2=right

    // Keyboard
    event.key;              // "Enter", "a", "ArrowUp"
    event.code;             // "KeyA" (physical key)
    event.shiftKey;
    event.ctrlKey;
    event.altKey;
    event.metaKey;          // Cmd / Windows key

    // Control
    event.preventDefault();   // stop default behavior
    event.stopPropagation();  // stop bubbling
});

Utility Patterns

// Debounce — wait until user stops
function debounce(fn, delay) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}

const searchInput = document.querySelector("#search");
searchInput.addEventListener("input", debounce((e) => {
    fetch(`/api/search?q=${e.target.value}`).then(r => r.json()).then(renderResults);
}, 300));

// Throttle — fire at most once per interval
function throttle(fn, limit) {
    let inThrottle;
    return (...args) => {
        if (!inThrottle) {
            fn(...args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

window.addEventListener("scroll", throttle(updateScrollProgress, 16), { passive: true });

Keyboard Trap (Modals)

function trapFocus(modal) {
    const focusable = modal.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const first = focusable[0];
    const last = focusable[focusable.length - 1];

    modal.addEventListener("keydown", (e) => {
        if (e.key !== "Tab") return;
        
        if (e.shiftKey) {
            if (document.activeElement === first) {
                last.focus();
                e.preventDefault();
            }
        } else {
            if (document.activeElement === last) {
                first.focus();
                e.preventDefault();
            }
        }
    });
    
    first.focus();
}

Next lesson: Fetch API & Async JavaScript — loading data from servers without page reloads.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!