javascript

examples

examples.js
/**
 * ============================================================
 * 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));
Examples - JavaScript Tutorial | DeepML