Event Bubbling and Delegation
Understanding event propagation in the DOM
Challenge
Section titled “Challenge”Efficiently handle events on dynamic elements and understand event flow.
Explanation
Section titled “Explanation”Events propagate through the DOM in phases: capture, target, and bubbling. Event delegation leverages this to handle many elements with few listeners.
Event Phases
Section titled “Event Phases”// Three phases of event propagationelement.addEventListener('click', handler, true); // Capture phaseelement.addEventListener('click', handler, false); // Bubbling phase (default)
// Phase 1: Capture (top-down)// Phase 2: Target (the clicked element)// Phase 3: Bubble (bottom-up)
// Document// ↓ (capture)// HTML// ↓// Body// ↓// Div// ↓// Button ← Clicked// ↑ (bubble)// Div// ↑// Body// ↑// HTML// ↑// DocumentEvent Propagation
Section titled “Event Propagation”const parent = document.getElementById('parent');const child = document.getElementById('child');
// Bubbling: child event triggers parent listenerschild.addEventListener('click', () => { console.log('Child clicked');});
parent.addEventListener('click', () => { console.log('Parent clicked (via bubbling)');});
// Clicking child logs:// 'Child clicked'// 'Parent clicked (via bubbling)'
// Stop propagationchild.addEventListener('click', (event) => { event.stopPropagation(); // Prevents bubbling console.log('Child clicked');});
// Now parent listener won't be calledEvent Delegation
Section titled “Event Delegation”// ❌ Inefficient: Add listener to each itemconst items = document.querySelectorAll('.item');items.forEach(item => { item.addEventListener('click', () => { item.classList.add('active'); });});
// ✅ Efficient: Single listener on parentconst list = document.getElementById('list');list.addEventListener('click', (event) => { if (event.target.matches('.item')) { event.target.classList.add('active'); }});
// Works for dynamically added items tooconst newItem = document.createElement('div');newItem.className = 'item';list.appendChild(newItem); // Already has click handler via delegationpreventDefault and stopPropagation
Section titled “preventDefault and stopPropagation”const link = document.querySelector('a');
// preventDefault: Cancel default actionlink.addEventListener('click', (event) => { event.preventDefault(); // Link won't navigate});
// stopPropagation: Stop bubblingconst nested = document.getElementById('nested');nested.addEventListener('click', (event) => { event.stopPropagation(); // Won't bubble to parent console.log('Nested clicked');});
// stopImmediatePropagation: Stop all handlerselement.addEventListener('click', (event) => { event.stopImmediatePropagation(); console.log('First');});
element.addEventListener('click', () => { console.log('Second'); // Won't execute});Event Object Properties
Section titled “Event Object Properties”element.addEventListener('click', (event) => { // Properties event.target; // Element that triggered event event.currentTarget; // Element with listener event.type; // 'click' event.timestamp; // When event occurred
// Methods event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
// Mouse events event.clientX, event.clientY; // Viewport coordinates event.pageX, event.pageY; // Page coordinates event.screenX, event.screenY; // Screen coordinates event.button; // 0=left, 1=middle, 2=right
// Keyboard events event.key; // 'Enter', 'Shift', etc event.code; // 'KeyA', 'ShiftLeft', etc event.ctrlKey; // Modifier keys event.shiftKey; event.altKey; event.metaKey;});Focus Events
Section titled “Focus Events”const input = document.querySelector('input');
// Focus: When element gets focusinput.addEventListener('focus', () => { input.classList.add('focused');});
// Blur: When element loses focusinput.addEventListener('blur', () => { input.classList.remove('focused');});
// focusin/focusout: Bubble versionsinput.addEventListener('focusin', () => { // Can be caught on parent});Practical Example
Section titled “Practical Example”// Rich form with dynamic elementsconst form = document.getElementById('dynamic-form');
form.addEventListener('click', (event) => { const btn = event.target.closest('button[data-action]'); if (!btn) return;
const action = btn.dataset.action;
switch(action) { case 'add': addField(); break; case 'remove': event.target.closest('.field').remove(); break; case 'submit': event.preventDefault(); submitForm(); break; }});
function addField() { const field = document.createElement('input'); field.type = 'text'; form.appendChild(field);}