SolutionRead Only
// EVENT PROPAGATION PHASES
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
// Capture phase (true parameter)
outer.addEventListener('click', () => {
console.log('Outer CAPTURE');
}, true);
middle.addEventListener('click', () => {
console.log('Middle CAPTURE');
}, true);
inner.addEventListener('click', () => {
console.log('Inner CAPTURE');
}, true);
// Bubble phase (default)
outer.addEventListener('click', () => {
console.log('Outer BUBBLE');
});
middle.addEventListener('click', () => {
console.log('Middle BUBBLE');
});
inner.addEventListener('click', () => {
console.log('Inner BUBBLE');
});
// When inner clicked, output:
// Outer CAPTURE
// Middle CAPTURE
// Inner CAPTURE
// Inner BUBBLE
// Middle BUBBLE
// Outer BUBBLE
// EVENT DELEGATION - Todo List
const todoList = document.getElementById('todoList');
// One listener for all todos
todoList.addEventListener('click', (event) => {
const target = event.target;
// Delete button clicked
if (target.classList.contains('delete')) {
const li = target.closest('li');
li.remove();
}
// Checkbox clicked
if (target.type === 'checkbox') {
const li = target.closest('li');
li.classList.toggle('completed');
}
// Edit button clicked
if (target.classList.contains('edit')) {
const li = target.closest('li');
const span = li.querySelector('.text');
span.contentEditable = true;
span.focus();
}
});
// Add todos dynamically (delegation handles them automatically)
function addTodo(text) {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox">
<span class="text">${text}</span>
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`;
todoList.appendChild(li);
}
// EVENT DELEGATION - Table
const table = document.getElementById('dataTable');
table.addEventListener('click', (event) => {
const target = event.target;
// Find the row (even if clicked on cell)
const row = target.closest('tr');
if (row && row.dataset.id) {
console.log('Row clicked:', row.dataset.id);
// Check which column
const cell = target.closest('td');
if (cell) {
const columnIndex = cell.cellIndex;
console.log('Column:', columnIndex);
// Handle specific columns
if (columnIndex === 3) { // Actions column
if (target.classList.contains('edit')) {
editRow(row.dataset.id);
}
if (target.classList.contains('delete')) {
deleteRow(row.dataset.id);
}
}
}
}
});
// CLICK OUTSIDE TO CLOSE
const dropdown = document.getElementById('dropdown');
const button = document.getElementById('dropdownBtn');
// Open dropdown
button.addEventListener('click', (event) => {
event.stopPropagation(); // Don't trigger document listener
dropdown.classList.add('open');
});
// Close when clicking outside
document.addEventListener('click', (event) => {
if (!dropdown.contains(event.target)) {
dropdown.classList.remove('open');
}
});
// Don't close when clicking inside dropdown
dropdown.addEventListener('click', (event) => {
event.stopPropagation();
});
// MODAL WITH EVENT DELEGATION
const modal = document.getElementById('modal');
const modalContent = modal.querySelector('.modal-content');
// Close on background click
modal.addEventListener('click', (event) => {
// Only close if clicked directly on modal, not children
if (event.target === modal) {
closeModal();
}
});
// Alternative: check if clicked outside content
modal.addEventListener('click', (event) => {
if (!modalContent.contains(event.target)) {
closeModal();
}
});
// NAVIGATION MENU
const nav = document.getElementById('nav');
nav.addEventListener('click', (event) => {
const link = event.target.closest('a');
if (link) {
// Check if has submenu
const parent = link.parentElement;
if (parent.querySelector('.submenu')) {
event.preventDefault();
parent.classList.toggle('open');
}
}
});
// FORM WITH DELEGATION
const form = document.getElementById('dynamicForm');
// Handle all inputs with delegation
form.addEventListener('input', (event) => {
const input = event.target;
if (input.type === 'email') {
validateEmail(input);
}
if (input.type === 'password') {
validatePassword(input);
}
if (input.hasAttribute('data-validate')) {
validateField(input);
}
});
// Handle all buttons
form.addEventListener('click', (event) => {
if (event.target.matches('.add-field')) {
addField();
}
if (event.target.matches('.remove-field')) {
event.target.closest('.field').remove();
}
});
// PERFORMANCE COMPARISON
// Bad: Many listeners (memory intensive)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// If 1000 items = 1000 listeners!
// Good: One listener with delegation
const container = document.getElementById('container');
container.addEventListener('click', (event) => {
if (event.target.classList.contains('item')) {
handleClick(event);
}
});
// Only 1 listener for 1000 items!
// STOPPING PROPAGATION
inner.addEventListener('click', (event) => {
console.log('Inner');
event.stopPropagation(); // Stops bubbling
// Outer handlers won't fire
});
// Stop other handlers on same element
button.addEventListener('click', (event) => {
console.log('First');
event.stopImmediatePropagation();
});
button.addEventListener('click', () => {
console.log('Second'); // Won't execute
});