Problem Statement
What is event delegation and why is it useful?
Explanation
Event delegation is attaching a single event listener to a parent element to handle events from its children using event bubbling.
Instead of adding listeners to many child elements, you add one listener to the parent. When a child is clicked, the event bubbles up to the parent.
Benefits include better performance with fewer event listeners, automatic handling of dynamically added elements, and less memory usage.
You check event.target to determine which child triggered the event. Then handle it accordingly based on the target.
Event delegation is a fundamental pattern in JavaScript, especially for lists and dynamic content.
Understanding delegation shows advanced knowledge of events and DOM manipulation.
Code Solution
SolutionRead Only
// WITHOUT EVENT DELEGATION (bad for many elements)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', () => {
console.log('Item clicked');
});
});
// Problems:
// - 100 items = 100 event listeners (memory!)
// - Doesn't work for dynamically added items
// WITH EVENT DELEGATION (efficient)
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
// Check if clicked element is an item
if (event.target.classList.contains('item')) {
console.log('Item clicked:', event.target.textContent);
}
});
// Benefits:
// - One listener for all items
// - Works for items added later
// - Better performance
// PRACTICAL EXAMPLE - Todo List
const todoList = document.getElementById('todoList');
todoList.addEventListener('click', (event) => {
const target = event.target;
// Handle delete button
if (target.classList.contains('delete-btn')) {
target.parentElement.remove();
}
// Handle checkbox
if (target.type === 'checkbox') {
target.parentElement.classList.toggle('completed');
}
// Handle edit button
if (target.classList.contains('edit-btn')) {
const li = target.parentElement;
const text = li.querySelector('.text');
text.contentEditable = true;
text.focus();
}
});
// Add new items dynamically
function addTodo(text) {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox">
<span class="text">${text}</span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
`;
todoList.appendChild(li);
// No need to add event listeners!
}
// ADVANCED DELEGATION - Multiple event types
const container = document.getElementById('container');
// Handle multiple events on parent
container.addEventListener('click', handleClick);
container.addEventListener('dblclick', handleDoubleClick);
container.addEventListener('contextmenu', handleRightClick);
function handleClick(event) {
if (event.target.matches('.button')) {
console.log('Button clicked');
}
}
function handleDoubleClick(event) {
if (event.target.matches('.item')) {
console.log('Item double clicked');
}
}
function handleRightClick(event) {
if (event.target.matches('.card')) {
event.preventDefault();
showContextMenu(event);
}
}
// CHECKING TARGET ELEMENT
// Method 1: classList.contains()
if (event.target.classList.contains('item')) { }
// Method 2: matches() - CSS selector
if (event.target.matches('.item')) { }
if (event.target.matches('button.primary')) { }
// Method 3: tagName
if (event.target.tagName === 'BUTTON') { }
// Method 4: closest() - find ancestor
const card = event.target.closest('.card');
if (card) {
// Clicked somewhere inside a card
}
// DELEGATION WITH CLOSEST
table.addEventListener('click', (event) => {
// Find the row, even if clicked on cell
const row = event.target.closest('tr');
if (row && row.dataset.id) {
console.log('Row clicked:', row.dataset.id);
}
});
// REAL-WORLD EXAMPLE - Image Gallery
const gallery = document.getElementById('gallery');
const modal = document.getElementById('modal');
const modalImg = document.getElementById('modalImg');
gallery.addEventListener('click', (event) => {
if (event.target.tagName === 'IMG') {
modal.style.display = 'block';
modalImg.src = event.target.src;
}
});
modal.addEventListener('click', () => {
modal.style.display = 'none';
});
// WHEN NOT TO USE DELEGATION
// 1. Events that don't bubble (focus, blur)
// 2. Need to bind this to specific element
// 3. Very specific single-element handler
// For non-bubbling events
const input = document.getElementById('myInput');
input.addEventListener('focus', () => {
// Must attach directly
});