javascript
examples
examples.js⚡javascript
/**
* ============================================================
* 10.4 Event Delegation - Examples
* ============================================================
*
* This file demonstrates event delegation patterns
* for efficient event handling.
*
* To run these examples:
* 1. Create an HTML file with appropriate elements
* 2. Link this script or paste into browser console
*/
// =============================================================
// EXAMPLE 1: Basic Delegation - Tag Matching
// =============================================================
console.log('=== Example 1: Basic Delegation - Tag Matching ===');
// HTML: <ul id="list1"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>
const list1 = document.getElementById('list1');
if (list1) {
list1.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked:', event.target.textContent);
}
});
console.log('Click on any list item to see delegation in action');
}
// =============================================================
// EXAMPLE 2: Class-Based Delegation
// =============================================================
console.log('\n=== Example 2: Class-Based Delegation ===');
// HTML: <div id="buttons"><button class="action-btn">A</button><button class="action-btn">B</button></div>
const buttons = document.getElementById('buttons');
if (buttons) {
buttons.addEventListener('click', (event) => {
if (event.target.classList.contains('action-btn')) {
console.log('Action button clicked:', event.target.textContent);
event.target.classList.toggle('active');
}
});
}
// =============================================================
// EXAMPLE 3: Using closest() for Nested Content
// =============================================================
console.log('\n=== Example 3: Using closest() for Nested Content ===');
// HTML:
// <ul id="list2">
// <li class="item"><span class="icon">📁</span><span class="name">File</span></li>
// </ul>
const list2 = document.getElementById('list2');
if (list2) {
list2.addEventListener('click', (event) => {
// Even if icon or name span is clicked, find the parent li
const item = event.target.closest('.item');
if (item) {
const name = item.querySelector('.name').textContent;
console.log('Selected item:', name);
}
});
console.log('Click on any part of an item (icon or name)');
}
// =============================================================
// EXAMPLE 4: Data Attribute Actions
// =============================================================
console.log('\n=== Example 4: Data Attribute Actions ===');
// HTML:
// <div id="toolbar">
// <button data-action="bold">B</button>
// <button data-action="italic">I</button>
// <button data-action="underline">U</button>
// </div>
const toolbar = document.getElementById('toolbar');
if (toolbar) {
toolbar.addEventListener('click', (event) => {
const button = event.target.closest('[data-action]');
if (button) {
const action = button.dataset.action;
console.log('Toolbar action:', action);
// Handle the action
switch (action) {
case 'bold':
document.execCommand('bold');
break;
case 'italic':
document.execCommand('italic');
break;
case 'underline':
document.execCommand('underline');
break;
}
}
});
}
// =============================================================
// EXAMPLE 5: Multiple Action Types
// =============================================================
console.log('\n=== Example 5: Multiple Action Types ===');
// HTML:
// <div id="card-container">
// <div class="card" data-id="1">
// <h3>Card Title</h3>
// <button class="edit-btn">Edit</button>
// <button class="delete-btn">Delete</button>
// </div>
// </div>
const cardContainer = document.getElementById('card-container');
if (cardContainer) {
cardContainer.addEventListener('click', (event) => {
const target = event.target;
const card = target.closest('.card');
if (!card) return;
const cardId = card.dataset.id;
if (target.classList.contains('edit-btn')) {
console.log('Edit card:', cardId);
} else if (target.classList.contains('delete-btn')) {
console.log('Delete card:', cardId);
card.remove();
} else {
console.log('Card clicked:', cardId);
}
});
}
// =============================================================
// EXAMPLE 6: Dynamic Content - Todo List
// =============================================================
console.log('\n=== Example 6: Dynamic Content - Todo List ===');
// HTML:
// <div id="todo-app">
// <input id="todo-input" placeholder="New todo">
// <button id="add-todo">Add</button>
// <ul id="todo-list"></ul>
// </div>
const todoApp = document.getElementById('todo-app');
const todoInput = document.getElementById('todo-input');
const addTodoBtn = document.getElementById('add-todo');
const todoList = document.getElementById('todo-list');
if (todoList) {
// Single delegated handler for all todo items
todoList.addEventListener('click', (event) => {
const target = event.target;
const todoItem = target.closest('li');
if (!todoItem) return;
if (target.classList.contains('complete-btn')) {
todoItem.classList.toggle('completed');
target.textContent = todoItem.classList.contains('completed')
? 'Undo'
: 'Done';
} else if (target.classList.contains('delete-btn')) {
todoItem.remove();
}
});
// Add new todos
if (addTodoBtn && todoInput) {
addTodoBtn.addEventListener('click', () => {
const text = todoInput.value.trim();
if (!text) return;
const li = document.createElement('li');
li.innerHTML = `
<span class="todo-text">${text}</span>
<button class="complete-btn">Done</button>
<button class="delete-btn">Delete</button>
`;
todoList.appendChild(li);
todoInput.value = '';
console.log('Todo added:', text);
});
}
}
// =============================================================
// EXAMPLE 7: Table Row Actions
// =============================================================
console.log('\n=== Example 7: Table Row Actions ===');
// HTML:
// <table id="data-table">
// <tbody>
// <tr data-id="1"><td>Row 1</td><td><button class="view">View</button></td></tr>
// </tbody>
// </table>
const dataTable = document.getElementById('data-table');
if (dataTable) {
dataTable.addEventListener('click', (event) => {
const button = event.target.closest('button');
const row = event.target.closest('tr');
if (!row) return;
const rowId = row.dataset.id;
if (button?.classList.contains('view')) {
console.log('View row:', rowId);
} else if (button?.classList.contains('edit')) {
console.log('Edit row:', rowId);
} else if (button?.classList.contains('delete')) {
console.log('Delete row:', rowId);
row.remove();
} else {
// Row itself was clicked
row.classList.toggle('selected');
}
});
}
// =============================================================
// EXAMPLE 8: Navigation Menu
// =============================================================
console.log('\n=== Example 8: Navigation Menu ===');
// HTML:
// <nav id="main-nav">
// <a href="#home" data-section="home">Home</a>
// <a href="#about" data-section="about">About</a>
// <a href="#contact" data-section="contact">Contact</a>
// </nav>
const mainNav = document.getElementById('main-nav');
if (mainNav) {
mainNav.addEventListener('click', (event) => {
const link = event.target.closest('a');
if (link) {
event.preventDefault();
// Remove active from all links
mainNav.querySelectorAll('a').forEach((a) => {
a.classList.remove('active');
});
// Add active to clicked link
link.classList.add('active');
const section = link.dataset.section;
console.log('Navigate to:', section);
// Show corresponding section
// document.querySelectorAll("section").forEach(s => s.hidden = true);
// document.getElementById(section).hidden = false;
}
});
}
// =============================================================
// EXAMPLE 9: Accordion/Collapsible
// =============================================================
console.log('\n=== Example 9: Accordion/Collapsible ===');
// HTML:
// <div id="accordion">
// <div class="accordion-item">
// <button class="accordion-header">Section 1</button>
// <div class="accordion-content">Content 1</div>
// </div>
// </div>
const accordion = document.getElementById('accordion');
if (accordion) {
accordion.addEventListener('click', (event) => {
const header = event.target.closest('.accordion-header');
if (header) {
const item = header.parentElement;
const content = item.querySelector('.accordion-content');
const isOpen = item.classList.contains('open');
// Close all items first (optional - for exclusive mode)
accordion.querySelectorAll('.accordion-item').forEach((i) => {
i.classList.remove('open');
i.querySelector('.accordion-content').style.maxHeight = '0';
});
// Toggle clicked item
if (!isOpen) {
item.classList.add('open');
content.style.maxHeight = content.scrollHeight + 'px';
}
}
});
}
// =============================================================
// EXAMPLE 10: Image Gallery
// =============================================================
console.log('\n=== Example 10: Image Gallery ===');
// HTML:
// <div id="gallery">
// <img src="1.jpg" data-full="1-full.jpg" alt="Image 1">
// <img src="2.jpg" data-full="2-full.jpg" alt="Image 2">
// </div>
// <div id="lightbox"><img id="lightbox-img"></div>
const gallery = document.getElementById('gallery');
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
if (gallery) {
gallery.addEventListener('click', (event) => {
const img = event.target.closest('img');
if (img && lightbox && lightboxImg) {
const fullSrc = img.dataset.full || img.src;
lightboxImg.src = fullSrc;
lightbox.classList.add('open');
console.log('Opening image:', fullSrc);
}
});
if (lightbox) {
lightbox.addEventListener('click', () => {
lightbox.classList.remove('open');
});
}
}
// =============================================================
// EXAMPLE 11: Dropdown Menu
// =============================================================
console.log('\n=== Example 11: Dropdown Menu ===');
// HTML:
// <div id="dropdown-container">
// <div class="dropdown">
// <button class="dropdown-toggle">Menu</button>
// <ul class="dropdown-menu">
// <li><a href="#">Option 1</a></li>
// <li><a href="#">Option 2</a></li>
// </ul>
// </div>
// </div>
const dropdownContainer = document.getElementById('dropdown-container');
if (dropdownContainer) {
dropdownContainer.addEventListener('click', (event) => {
const toggle = event.target.closest('.dropdown-toggle');
const menuItem = event.target.closest('.dropdown-menu a');
if (toggle) {
const dropdown = toggle.parentElement;
dropdown.classList.toggle('open');
} else if (menuItem) {
event.preventDefault();
console.log('Selected:', menuItem.textContent);
// Close dropdown
menuItem.closest('.dropdown').classList.remove('open');
}
});
// Close on outside click
document.addEventListener('click', (event) => {
if (!event.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.open').forEach((d) => {
d.classList.remove('open');
});
}
});
}
// =============================================================
// EXAMPLE 12: Tab Component
// =============================================================
console.log('\n=== Example 12: Tab Component ===');
// HTML:
// <div id="tabs">
// <div class="tab-list">
// <button class="tab" data-tab="tab1">Tab 1</button>
// <button class="tab" data-tab="tab2">Tab 2</button>
// </div>
// <div class="tab-panels">
// <div id="tab1" class="tab-panel">Content 1</div>
// <div id="tab2" class="tab-panel">Content 2</div>
// </div>
// </div>
const tabs = document.getElementById('tabs');
if (tabs) {
const tabList = tabs.querySelector('.tab-list');
tabList.addEventListener('click', (event) => {
const tab = event.target.closest('.tab');
if (tab) {
const tabId = tab.dataset.tab;
// Update tabs
tabList.querySelectorAll('.tab').forEach((t) => {
t.classList.remove('active');
t.setAttribute('aria-selected', 'false');
});
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
// Update panels
tabs.querySelectorAll('.tab-panel').forEach((p) => {
p.classList.remove('active');
p.hidden = true;
});
const panel = document.getElementById(tabId);
if (panel) {
panel.classList.add('active');
panel.hidden = false;
}
console.log('Switched to tab:', tabId);
}
});
}
// =============================================================
// EXAMPLE 13: Tag Input/Chips
// =============================================================
console.log('\n=== Example 13: Tag Input/Chips ===');
// HTML:
// <div id="tag-input">
// <div id="tags"></div>
// <input id="tag-text" placeholder="Add tag">
// </div>
const tagInput = document.getElementById('tag-input');
const tagsContainer = document.getElementById('tags');
const tagText = document.getElementById('tag-text');
if (tagsContainer && tagText) {
// Delegate delete button clicks
tagsContainer.addEventListener('click', (event) => {
const deleteBtn = event.target.closest('.tag-delete');
if (deleteBtn) {
const tag = deleteBtn.parentElement;
console.log('Removed tag:', tag.dataset.value);
tag.remove();
}
});
// Add new tag on Enter
tagText.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && tagText.value.trim()) {
const value = tagText.value.trim();
const tag = document.createElement('span');
tag.className = 'tag';
tag.dataset.value = value;
tag.innerHTML = `
${value}
<button class="tag-delete">×</button>
`;
tagsContainer.appendChild(tag);
tagText.value = '';
console.log('Added tag:', value);
}
});
}
// =============================================================
// EXAMPLE 14: Sortable List (Drag Handle)
// =============================================================
console.log('\n=== Example 14: Sortable List ===');
// HTML:
// <ul id="sortable-list">
// <li><span class="handle">≡</span> Item 1</li>
// <li><span class="handle">≡</span> Item 2</li>
// </ul>
const sortableList = document.getElementById('sortable-list');
let draggedItem = null;
if (sortableList) {
sortableList.addEventListener('mousedown', (event) => {
const handle = event.target.closest('.handle');
if (handle) {
draggedItem = handle.parentElement;
draggedItem.classList.add('dragging');
}
});
sortableList.addEventListener('mousemove', (event) => {
if (!draggedItem) return;
const afterElement = getDragAfterElement(sortableList, event.clientY);
if (afterElement) {
sortableList.insertBefore(draggedItem, afterElement);
} else {
sortableList.appendChild(draggedItem);
}
});
document.addEventListener('mouseup', () => {
if (draggedItem) {
draggedItem.classList.remove('dragging');
draggedItem = null;
}
});
}
function getDragAfterElement(container, y) {
const draggableElements = [
...container.querySelectorAll('li:not(.dragging)'),
];
return draggableElements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset, element: child };
}
return closest;
},
{ offset: Number.NEGATIVE_INFINITY }
).element;
}
// =============================================================
// EXAMPLE 15: Action Router Pattern
// =============================================================
console.log('\n=== Example 15: Action Router Pattern ===');
// Define action handlers
const actionHandlers = {
openModal(element) {
const modalId = element.dataset.modal;
console.log('Opening modal:', modalId);
const modal = document.getElementById(modalId);
if (modal) modal.classList.add('open');
},
closeModal(element) {
const modal = element.closest('.modal');
if (modal) modal.classList.remove('open');
console.log('Modal closed');
},
toggleTheme() {
document.body.classList.toggle('dark-theme');
console.log('Theme toggled');
},
copyText(element) {
const text = element.dataset.text || element.textContent;
navigator.clipboard.writeText(text);
console.log('Copied:', text);
},
deleteItem(element) {
const itemId = element.dataset.itemId;
const item = element.closest('[data-deletable]');
if (item) {
item.remove();
console.log('Deleted item:', itemId);
}
},
};
// Single delegated handler for all actions
document.addEventListener('click', (event) => {
const actionElement = event.target.closest('[data-action]');
if (actionElement) {
const action = actionElement.dataset.action;
if (actionHandlers[action]) {
event.preventDefault();
actionHandlers[action](actionElement);
} else {
console.warn('Unknown action:', action);
}
}
});
console.log('Action router active. Use data-action attribute on elements.');
// =============================================================
// EXAMPLE 16: Form Field Delegation
// =============================================================
console.log('\n=== Example 16: Form Field Delegation ===');
// Use focusin/focusout (they bubble, unlike focus/blur)
const form = document.getElementById('delegated-form');
if (form) {
// Focus events
form.addEventListener('focusin', (event) => {
const field = event.target.closest('.form-field');
if (field) {
field.classList.add('focused');
console.log('Field focused:', event.target.name);
}
});
form.addEventListener('focusout', (event) => {
const field = event.target.closest('.form-field');
if (field) {
field.classList.remove('focused');
// Validate on blur
if (event.target.required && !event.target.value) {
field.classList.add('error');
} else {
field.classList.remove('error');
}
}
});
// Input events
form.addEventListener('input', (event) => {
const field = event.target.closest('.form-field');
if (field) {
// Real-time validation feedback
const isValid = event.target.checkValidity();
field.classList.toggle('valid', isValid && event.target.value);
field.classList.remove('error');
}
});
}
// =============================================================
// EXAMPLE 17: Keyboard Navigation
// =============================================================
console.log('\n=== Example 17: Keyboard Navigation ===');
const keyboardNav = document.getElementById('keyboard-nav');
if (keyboardNav) {
const items = keyboardNav.querySelectorAll('.nav-item');
let currentIndex = 0;
keyboardNav.addEventListener('keydown', (event) => {
if (!['ArrowUp', 'ArrowDown', 'Enter', ' '].includes(event.key)) return;
event.preventDefault();
if (event.key === 'ArrowDown') {
items[currentIndex].classList.remove('highlighted');
currentIndex = (currentIndex + 1) % items.length;
items[currentIndex].classList.add('highlighted');
items[currentIndex].focus();
} else if (event.key === 'ArrowUp') {
items[currentIndex].classList.remove('highlighted');
currentIndex = (currentIndex - 1 + items.length) % items.length;
items[currentIndex].classList.add('highlighted');
items[currentIndex].focus();
} else if (event.key === 'Enter' || event.key === ' ') {
console.log('Selected:', items[currentIndex].textContent);
}
});
}
// =============================================================
// EXAMPLE 18: Context Menu
// =============================================================
console.log('\n=== Example 18: Context Menu ===');
const contextMenuContainer = document.getElementById('context-menu-container');
const contextMenu = document.getElementById('context-menu');
if (contextMenuContainer && contextMenu) {
// Show context menu on right-click
contextMenuContainer.addEventListener('contextmenu', (event) => {
event.preventDefault();
const item = event.target.closest('[data-context-id]');
if (item) {
contextMenu.dataset.targetId = item.dataset.contextId;
contextMenu.style.left = event.pageX + 'px';
contextMenu.style.top = event.pageY + 'px';
contextMenu.classList.add('visible');
console.log('Context menu for:', item.dataset.contextId);
}
});
// Handle context menu actions
contextMenu.addEventListener('click', (event) => {
const action = event.target.closest('[data-menu-action]');
if (action) {
const menuAction = action.dataset.menuAction;
const targetId = contextMenu.dataset.targetId;
console.log('Action:', menuAction, 'on item:', targetId);
contextMenu.classList.remove('visible');
}
});
// Hide on outside click
document.addEventListener('click', () => {
contextMenu.classList.remove('visible');
});
}
// =============================================================
// EXAMPLE 19: Virtual Scrolling Helper
// =============================================================
console.log('\n=== Example 19: Virtual Scrolling Helper ===');
// For very large lists, delegate to a scrollable container
const virtualList = document.getElementById('virtual-list');
if (virtualList) {
virtualList.addEventListener('click', (event) => {
const item = event.target.closest('.virtual-item');
if (item) {
const index = parseInt(item.dataset.index, 10);
console.log('Clicked virtual item at index:', index);
}
});
// Simulated render function
function renderVisibleItems(startIndex, count) {
virtualList.innerHTML = '';
for (let i = startIndex; i < startIndex + count; i++) {
const item = document.createElement('div');
item.className = 'virtual-item';
item.dataset.index = i;
item.textContent = `Item ${i}`;
virtualList.appendChild(item);
}
}
// renderVisibleItems(0, 20);
}
// =============================================================
// EXAMPLE 20: Complete Widget Example
// =============================================================
console.log('\n=== Example 20: Complete Widget Example ===');
// A fully delegated color picker widget
const colorPicker = document.getElementById('color-picker');
if (colorPicker) {
const colors = [
'#e74c3c',
'#3498db',
'#2ecc71',
'#f39c12',
'#9b59b6',
'#1abc9c',
];
// Generate color swatches
const swatchContainer = colorPicker.querySelector('.swatches') || colorPicker;
colors.forEach((color) => {
const swatch = document.createElement('button');
swatch.className = 'swatch';
swatch.dataset.color = color;
swatch.style.backgroundColor = color;
swatchContainer.appendChild(swatch);
});
// Single delegated handler
colorPicker.addEventListener('click', (event) => {
const swatch = event.target.closest('.swatch');
const clearBtn = event.target.closest('.clear-btn');
if (swatch) {
// Remove selected from all
colorPicker.querySelectorAll('.swatch').forEach((s) => {
s.classList.remove('selected');
});
// Select this swatch
swatch.classList.add('selected');
const color = swatch.dataset.color;
console.log('Selected color:', color);
// Dispatch custom event
colorPicker.dispatchEvent(
new CustomEvent('colorchange', {
detail: { color },
})
);
} else if (clearBtn) {
colorPicker.querySelectorAll('.swatch').forEach((s) => {
s.classList.remove('selected');
});
console.log('Color cleared');
}
});
// Listen for color change
colorPicker.addEventListener('colorchange', (event) => {
console.log('Color changed to:', event.detail.color);
});
}
// =============================================================
// Summary
// =============================================================
console.log('\n' + '='.repeat(60));
console.log('Examples Summary:');
console.log('='.repeat(60));
console.log('1-3: Basic delegation patterns');
console.log('4-6: Data attributes and dynamic content');
console.log('7-9: Tables, navigation, accordions');
console.log('10-12: Galleries, dropdowns, tabs');
console.log('13-15: Tags, sorting, action routing');
console.log('16-18: Forms, keyboard nav, context menus');
console.log('19-20: Virtual scrolling, complete widget');
console.log('='.repeat(60));