javascript

exercises

exercises.js
/**
 * ============================================================
 * 10.4 Event Delegation - Exercises
 * ============================================================
 *
 * Practice event delegation patterns for efficient
 * event handling and dynamic content.
 *
 * Instructions:
 * 1. Read each exercise description carefully
 * 2. Write your solution in the provided function
 * 3. Test with the provided HTML structure
 * 4. Check the hints and solutions if needed
 */

// =============================================================
// EXERCISE 1: Basic List Delegation
// =============================================================
/**
 * Add a delegated click handler to a list
 * When any list item is clicked, log its text content
 *
 * @param {string} listId - ID of the <ul> or <ol> element
 * @returns {boolean} - True if successful
 *
 * Example HTML: <ul id="myList"><li>Item 1</li><li>Item 2</li></ul>
 */
function delegateListClicks(listId) {
  // Your code here
}

// Test
// delegateListClicks("myList");

/*
 * HINT: Check if event.target.tagName === "LI"
 *
 * SOLUTION:
 * function delegateListClicks(listId) {
 *     const list = document.getElementById(listId);
 *     if (!list) return false;
 *
 *     list.addEventListener("click", (event) => {
 *         if (event.target.tagName === "LI") {
 *             console.log("Clicked:", event.target.textContent);
 *         }
 *     });
 *
 *     return true;
 * }
 */

// =============================================================
// EXERCISE 2: Button Group Handler
// =============================================================
/**
 * Handle clicks on buttons within a container
 * Buttons have data-value attribute
 * Return the selected value
 *
 * @param {string} containerId - ID of the button container
 * @param {Function} onSelect - Callback with selected value
 * @returns {boolean} - True if successful
 *
 * Example HTML:
 * <div id="options">
 *   <button data-value="a">Option A</button>
 *   <button data-value="b">Option B</button>
 * </div>
 */
function handleButtonGroup(containerId, onSelect) {
  // Your code here
}

// Test
// handleButtonGroup("options", (value) => console.log("Selected:", value));

/*
 * HINT: Use closest("[data-value]") to find the button
 *
 * SOLUTION:
 * function handleButtonGroup(containerId, onSelect) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return false;
 *
 *     container.addEventListener("click", (event) => {
 *         const button = event.target.closest("[data-value]");
 *
 *         if (button) {
 *             // Remove active from siblings
 *             container.querySelectorAll("[data-value]").forEach(btn => {
 *                 btn.classList.remove("active");
 *             });
 *
 *             // Add active to selected
 *             button.classList.add("active");
 *
 *             onSelect(button.dataset.value);
 *         }
 *     });
 *
 *     return true;
 * }
 */

// =============================================================
// EXERCISE 3: Nested Content Handling
// =============================================================
/**
 * Handle clicks on cards with nested content
 * Cards have structure: .card > .card-header + .card-body + .card-actions
 * Only handle clicks on .card-actions buttons
 *
 * @param {string} containerId - ID of cards container
 * @param {Object} handlers - { edit: fn, delete: fn, view: fn }
 * @returns {boolean} - True if successful
 */
function handleCardActions(containerId, handlers) {
  // Your code here
}

// Test
// handleCardActions("cards", {
//     edit: (id) => console.log("Edit:", id),
//     delete: (id) => console.log("Delete:", id),
//     view: (id) => console.log("View:", id)
// });

/*
 * HINT: Use closest(".card") to get the card, check button classes
 *
 * SOLUTION:
 * function handleCardActions(containerId, handlers) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return false;
 *
 *     container.addEventListener("click", (event) => {
 *         const button = event.target.closest("button");
 *         const card = event.target.closest(".card");
 *
 *         if (!button || !card) return;
 *
 *         const cardId = card.dataset.id;
 *
 *         if (button.classList.contains("edit-btn") && handlers.edit) {
 *             handlers.edit(cardId);
 *         } else if (button.classList.contains("delete-btn") && handlers.delete) {
 *             handlers.delete(cardId);
 *         } else if (button.classList.contains("view-btn") && handlers.view) {
 *             handlers.view(cardId);
 *         }
 *     });
 *
 *     return true;
 * }
 */

// =============================================================
// EXERCISE 4: Dynamic Todo List
// =============================================================
/**
 * Create a todo list with delegation for:
 * - Toggle complete on checkbox click
 * - Delete on delete button click
 * - Edit on double-click on text
 *
 * @param {string} listId - ID of the todo list
 * @returns {Object} - { addTodo(text), getTodos(), clearCompleted() }
 */
function createTodoList(listId) {
  // Your code here
}

// Test
// const todos = createTodoList("todo-list");
// todos.addTodo("Learn delegation");
// todos.addTodo("Practice JavaScript");

/*
 * HINT: Set up multiple delegated handlers for different interactions
 *
 * SOLUTION:
 * function createTodoList(listId) {
 *     const list = document.getElementById(listId);
 *     if (!list) return null;
 *
 *     let todoId = 0;
 *
 *     // Delegated click handler
 *     list.addEventListener("click", (event) => {
 *         const item = event.target.closest(".todo-item");
 *         if (!item) return;
 *
 *         if (event.target.matches(".todo-checkbox")) {
 *             item.classList.toggle("completed");
 *         } else if (event.target.matches(".todo-delete")) {
 *             item.remove();
 *         }
 *     });
 *
 *     // Double-click to edit
 *     list.addEventListener("dblclick", (event) => {
 *         const text = event.target.closest(".todo-text");
 *         if (!text) return;
 *
 *         const currentText = text.textContent;
 *         const input = document.createElement("input");
 *         input.value = currentText;
 *         input.className = "todo-edit";
 *
 *         text.replaceWith(input);
 *         input.focus();
 *
 *         input.addEventListener("blur", () => {
 *             const newText = document.createElement("span");
 *             newText.className = "todo-text";
 *             newText.textContent = input.value || currentText;
 *             input.replaceWith(newText);
 *         });
 *
 *         input.addEventListener("keydown", (e) => {
 *             if (e.key === "Enter") input.blur();
 *             if (e.key === "Escape") {
 *                 input.value = currentText;
 *                 input.blur();
 *             }
 *         });
 *     });
 *
 *     return {
 *         addTodo(text) {
 *             const item = document.createElement("li");
 *             item.className = "todo-item";
 *             item.dataset.id = ++todoId;
 *             item.innerHTML = `
 *                 <input type="checkbox" class="todo-checkbox">
 *                 <span class="todo-text">${text}</span>
 *                 <button class="todo-delete">×</button>
 *             `;
 *             list.appendChild(item);
 *             return todoId;
 *         },
 *
 *         getTodos() {
 *             return Array.from(list.querySelectorAll(".todo-item")).map(item => ({
 *                 id: item.dataset.id,
 *                 text: item.querySelector(".todo-text")?.textContent,
 *                 completed: item.classList.contains("completed")
 *             }));
 *         },
 *
 *         clearCompleted() {
 *             list.querySelectorAll(".todo-item.completed").forEach(item => item.remove());
 *         }
 *     };
 * }
 */

// =============================================================
// EXERCISE 5: Action Router
// =============================================================
/**
 * Create an action router that handles data-action attributes
 *
 * @param {string} containerId - ID of the container
 * @param {Object} actions - Object mapping action names to handlers
 * @returns {Object} - { addAction(name, handler), removeAction(name) }
 *
 * HTML usage: <button data-action="save">Save</button>
 */
function createActionRouter(containerId, actions = {}) {
  // Your code here
}

// Test
// const router = createActionRouter("app", {
//     save: () => console.log("Saving..."),
//     cancel: () => console.log("Cancelled")
// });
// router.addAction("delete", () => console.log("Deleting..."));

/*
 * HINT: Store actions in an object, look up by data-action value
 *
 * SOLUTION:
 * function createActionRouter(containerId, actions = {}) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return null;
 *
 *     const actionMap = { ...actions };
 *
 *     container.addEventListener("click", (event) => {
 *         const element = event.target.closest("[data-action]");
 *
 *         if (element) {
 *             const action = element.dataset.action;
 *
 *             if (actionMap[action]) {
 *                 event.preventDefault();
 *                 actionMap[action](element, event);
 *             }
 *         }
 *     });
 *
 *     return {
 *         addAction(name, handler) {
 *             actionMap[name] = handler;
 *         },
 *
 *         removeAction(name) {
 *             delete actionMap[name];
 *         }
 *     };
 * }
 */

// =============================================================
// EXERCISE 6: Table with Sorting
// =============================================================
/**
 * Make table headers clickable for sorting
 * Use delegation on thead for header clicks
 *
 * @param {string} tableId - ID of the table
 * @param {Function} onSort - Callback with (column, direction)
 * @returns {Object} - { getSortState() }
 */
function createSortableTable(tableId, onSort) {
  // Your code here
}

// Test
// const table = createSortableTable("data-table", (col, dir) => {
//     console.log(`Sort by ${col} ${dir}`);
// });

/*
 * HINT: Track current sort column and toggle direction on re-click
 *
 * SOLUTION:
 * function createSortableTable(tableId, onSort) {
 *     const table = document.getElementById(tableId);
 *     if (!table) return null;
 *
 *     const thead = table.querySelector("thead");
 *     if (!thead) return null;
 *
 *     let currentColumn = null;
 *     let currentDirection = "asc";
 *
 *     thead.addEventListener("click", (event) => {
 *         const th = event.target.closest("th[data-sort]");
 *
 *         if (th) {
 *             const column = th.dataset.sort;
 *
 *             // Toggle direction if same column
 *             if (column === currentColumn) {
 *                 currentDirection = currentDirection === "asc" ? "desc" : "asc";
 *             } else {
 *                 currentColumn = column;
 *                 currentDirection = "asc";
 *             }
 *
 *             // Update visual indicators
 *             thead.querySelectorAll("th").forEach(header => {
 *                 header.classList.remove("sort-asc", "sort-desc");
 *             });
 *             th.classList.add(`sort-${currentDirection}`);
 *
 *             onSort(column, currentDirection);
 *         }
 *     });
 *
 *     return {
 *         getSortState() {
 *             return { column: currentColumn, direction: currentDirection };
 *         }
 *     };
 * }
 */

// =============================================================
// EXERCISE 7: Accordion Component
// =============================================================
/**
 * Create an accordion with delegated expand/collapse
 *
 * @param {string} containerId - ID of the accordion container
 * @param {Object} options - { exclusive: boolean, animated: boolean }
 * @returns {Object} - { expandAll(), collapseAll(), toggle(index) }
 */
function createAccordion(containerId, options = {}) {
  // Your code here
}

// Test
// const accordion = createAccordion("faq", { exclusive: true, animated: true });

/*
 * HINT: Toggle open class on accordion items, handle exclusive mode
 *
 * SOLUTION:
 * function createAccordion(containerId, options = {}) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return null;
 *
 *     const { exclusive = false, animated = true } = options;
 *
 *     function toggleItem(item, forceOpen = null) {
 *         const content = item.querySelector(".accordion-content");
 *         const isOpen = item.classList.contains("open");
 *         const shouldOpen = forceOpen !== null ? forceOpen : !isOpen;
 *
 *         if (shouldOpen) {
 *             item.classList.add("open");
 *             if (animated) {
 *                 content.style.maxHeight = content.scrollHeight + "px";
 *             }
 *         } else {
 *             item.classList.remove("open");
 *             if (animated) {
 *                 content.style.maxHeight = "0";
 *             }
 *         }
 *     }
 *
 *     container.addEventListener("click", (event) => {
 *         const header = event.target.closest(".accordion-header");
 *         if (!header) return;
 *
 *         const item = header.parentElement;
 *
 *         if (exclusive) {
 *             container.querySelectorAll(".accordion-item").forEach(i => {
 *                 if (i !== item) toggleItem(i, false);
 *             });
 *         }
 *
 *         toggleItem(item);
 *     });
 *
 *     return {
 *         expandAll() {
 *             container.querySelectorAll(".accordion-item").forEach(item => {
 *                 toggleItem(item, true);
 *             });
 *         },
 *
 *         collapseAll() {
 *             container.querySelectorAll(".accordion-item").forEach(item => {
 *                 toggleItem(item, false);
 *             });
 *         },
 *
 *         toggle(index) {
 *             const items = container.querySelectorAll(".accordion-item");
 *             if (items[index]) toggleItem(items[index]);
 *         }
 *     };
 * }
 */

// =============================================================
// EXERCISE 8: Tab Navigation
// =============================================================
/**
 * Create tab navigation with delegation
 *
 * @param {string} containerId - ID of the tab container
 * @param {Function} onChange - Callback when tab changes
 * @returns {Object} - { activateTab(id), getActiveTab() }
 */
function createTabs(containerId, onChange) {
  // Your code here
}

// Test
// const tabs = createTabs("product-tabs", (tabId) => console.log("Tab:", tabId));
// tabs.activateTab("reviews");

/*
 * HINT: Handle tab button clicks, show/hide corresponding panels
 *
 * SOLUTION:
 * function createTabs(containerId, onChange) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return null;
 *
 *     const tabList = container.querySelector(".tab-list");
 *     let activeTabId = null;
 *
 *     function activateTab(tabId) {
 *         // Deactivate all tabs
 *         tabList.querySelectorAll(".tab").forEach(tab => {
 *             tab.classList.remove("active");
 *             tab.setAttribute("aria-selected", "false");
 *         });
 *
 *         // Hide all panels
 *         container.querySelectorAll(".tab-panel").forEach(panel => {
 *             panel.classList.remove("active");
 *             panel.hidden = true;
 *         });
 *
 *         // Activate selected tab
 *         const tab = tabList.querySelector(`[data-tab="${tabId}"]`);
 *         const panel = container.querySelector(`#${tabId}`);
 *
 *         if (tab && panel) {
 *             tab.classList.add("active");
 *             tab.setAttribute("aria-selected", "true");
 *             panel.classList.add("active");
 *             panel.hidden = false;
 *             activeTabId = tabId;
 *
 *             if (onChange) onChange(tabId);
 *         }
 *     }
 *
 *     tabList.addEventListener("click", (event) => {
 *         const tab = event.target.closest("[data-tab]");
 *         if (tab) {
 *             activateTab(tab.dataset.tab);
 *         }
 *     });
 *
 *     // Keyboard navigation
 *     tabList.addEventListener("keydown", (event) => {
 *         const tabs = [...tabList.querySelectorAll(".tab")];
 *         const currentIndex = tabs.findIndex(t => t.classList.contains("active"));
 *
 *         if (event.key === "ArrowRight") {
 *             const nextIndex = (currentIndex + 1) % tabs.length;
 *             activateTab(tabs[nextIndex].dataset.tab);
 *             tabs[nextIndex].focus();
 *         } else if (event.key === "ArrowLeft") {
 *             const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
 *             activateTab(tabs[prevIndex].dataset.tab);
 *             tabs[prevIndex].focus();
 *         }
 *     });
 *
 *     return {
 *         activateTab,
 *         getActiveTab() { return activeTabId; }
 *     };
 * }
 */

// =============================================================
// EXERCISE 9: Dropdown Menu
// =============================================================
/**
 * Create a dropdown menu with delegation
 * Handle toggle, option selection, and outside clicks
 *
 * @param {string} containerId - ID of the dropdown container
 * @param {Function} onSelect - Callback when option is selected
 * @returns {Object} - { open(), close(), getSelected() }
 */
function createDropdown(containerId, onSelect) {
  // Your code here
}

// Test
// const dropdown = createDropdown("country-select", (value, text) => {
//     console.log("Selected:", value, text);
// });

/*
 * HINT: Handle toggle click, option clicks, and document clicks for closing
 *
 * SOLUTION:
 * function createDropdown(containerId, onSelect) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return null;
 *
 *     const toggle = container.querySelector(".dropdown-toggle");
 *     const menu = container.querySelector(".dropdown-menu");
 *     let selectedValue = null;
 *     let selectedText = null;
 *
 *     function open() {
 *         container.classList.add("open");
 *         toggle.setAttribute("aria-expanded", "true");
 *     }
 *
 *     function close() {
 *         container.classList.remove("open");
 *         toggle.setAttribute("aria-expanded", "false");
 *     }
 *
 *     container.addEventListener("click", (event) => {
 *         if (event.target.closest(".dropdown-toggle")) {
 *             container.classList.toggle("open");
 *         }
 *
 *         const option = event.target.closest(".dropdown-option");
 *         if (option) {
 *             selectedValue = option.dataset.value;
 *             selectedText = option.textContent;
 *
 *             // Update toggle text
 *             toggle.textContent = selectedText;
 *
 *             // Update active state
 *             menu.querySelectorAll(".dropdown-option").forEach(opt => {
 *                 opt.classList.remove("active");
 *             });
 *             option.classList.add("active");
 *
 *             close();
 *
 *             if (onSelect) onSelect(selectedValue, selectedText);
 *         }
 *     });
 *
 *     // Close on outside click
 *     document.addEventListener("click", (event) => {
 *         if (!container.contains(event.target)) {
 *             close();
 *         }
 *     });
 *
 *     return {
 *         open,
 *         close,
 *         getSelected() { return { value: selectedValue, text: selectedText }; }
 *     };
 * }
 */

// =============================================================
// EXERCISE 10: Tag Input
// =============================================================
/**
 * Create a tag input component with delegation
 * - Add tags on Enter
 * - Remove tags on click
 * - Prevent duplicates
 *
 * @param {string} containerId - ID of the tag input container
 * @param {Object} options - { maxTags, allowDuplicates }
 * @returns {Object} - { getTags(), addTag(text), removeTag(text), clear() }
 */
function createTagInput(containerId, options = {}) {
  // Your code here
}

// Test
// const tags = createTagInput("tag-input", { maxTags: 5 });
// tags.addTag("javascript");
// tags.addTag("html");

/*
 * HINT: Store tags in an array, delegate remove button clicks
 *
 * SOLUTION:
 * function createTagInput(containerId, options = {}) {
 *     const container = document.getElementById(containerId);
 *     if (!container) return null;
 *
 *     const { maxTags = Infinity, allowDuplicates = false } = options;
 *     const tagsContainer = container.querySelector(".tags") || container;
 *     const input = container.querySelector("input");
 *     const tags = [];
 *
 *     function renderTags() {
 *         const tagElements = tagsContainer.querySelectorAll(".tag");
 *         tagElements.forEach(el => el.remove());
 *
 *         tags.forEach(tag => {
 *             const tagEl = document.createElement("span");
 *             tagEl.className = "tag";
 *             tagEl.dataset.value = tag;
 *             tagEl.innerHTML = `${tag}<button class="tag-remove">×</button>`;
 *             tagsContainer.insertBefore(tagEl, input);
 *         });
 *     }
 *
 *     function addTag(text) {
 *         const normalized = text.trim().toLowerCase();
 *         if (!normalized) return false;
 *         if (tags.length >= maxTags) return false;
 *         if (!allowDuplicates && tags.includes(normalized)) return false;
 *
 *         tags.push(normalized);
 *         renderTags();
 *         return true;
 *     }
 *
 *     function removeTag(text) {
 *         const index = tags.indexOf(text.toLowerCase());
 *         if (index > -1) {
 *             tags.splice(index, 1);
 *             renderTags();
 *             return true;
 *         }
 *         return false;
 *     }
 *
 *     // Delegated remove handler
 *     tagsContainer.addEventListener("click", (event) => {
 *         if (event.target.classList.contains("tag-remove")) {
 *             const tag = event.target.parentElement;
 *             removeTag(tag.dataset.value);
 *         }
 *     });
 *
 *     // Add on Enter
 *     if (input) {
 *         input.addEventListener("keydown", (event) => {
 *             if (event.key === "Enter") {
 *                 event.preventDefault();
 *                 addTag(input.value);
 *                 input.value = "";
 *             }
 *         });
 *     }
 *
 *     return {
 *         getTags() { return [...tags]; },
 *         addTag,
 *         removeTag,
 *         clear() {
 *             tags.length = 0;
 *             renderTags();
 *         }
 *     };
 * }
 */

// =============================================================
// BONUS CHALLENGE: Data Table with CRUD
// =============================================================
/**
 * Create a complete data table with CRUD operations
 * All operations should use event delegation
 *
 * @param {string} tableId - ID of the table
 * @param {Object} options - Configuration options
 * @returns {Object} - { addRow(data), updateRow(id, data), deleteRow(id), getData() }
 *
 * Features:
 * - Inline editing on cell double-click
 * - Delete button per row
 * - Select/deselect rows
 * - Bulk operations
 */
function createDataTable(tableId, options = {}) {
  // Your code here
}

/*
 * SOLUTION:
 * function createDataTable(tableId, options = {}) {
 *     const table = document.getElementById(tableId);
 *     if (!table) return null;
 *
 *     const tbody = table.querySelector("tbody") || table;
 *     let nextId = 1;
 *     const data = new Map();
 *     const selectedRows = new Set();
 *
 *     // Click handler for buttons
 *     tbody.addEventListener("click", (event) => {
 *         const row = event.target.closest("tr");
 *         if (!row) return;
 *
 *         const rowId = row.dataset.id;
 *
 *         if (event.target.matches(".delete-btn")) {
 *             deleteRow(rowId);
 *         } else if (event.target.matches(".select-checkbox")) {
 *             if (event.target.checked) {
 *                 selectedRows.add(rowId);
 *                 row.classList.add("selected");
 *             } else {
 *                 selectedRows.delete(rowId);
 *                 row.classList.remove("selected");
 *             }
 *         }
 *     });
 *
 *     // Double-click for inline editing
 *     tbody.addEventListener("dblclick", (event) => {
 *         const cell = event.target.closest("td[data-field]");
 *         if (!cell) return;
 *
 *         const row = cell.closest("tr");
 *         const field = cell.dataset.field;
 *         const currentValue = cell.textContent;
 *
 *         const input = document.createElement("input");
 *         input.value = currentValue;
 *         input.className = "inline-edit";
 *         cell.textContent = "";
 *         cell.appendChild(input);
 *         input.focus();
 *         input.select();
 *
 *         const save = () => {
 *             const newValue = input.value;
 *             cell.textContent = newValue;
 *
 *             // Update data
 *             const rowData = data.get(row.dataset.id);
 *             if (rowData) {
 *                 rowData[field] = newValue;
 *             }
 *         };
 *
 *         input.addEventListener("blur", save);
 *         input.addEventListener("keydown", (e) => {
 *             if (e.key === "Enter") input.blur();
 *             if (e.key === "Escape") {
 *                 input.value = currentValue;
 *                 input.blur();
 *             }
 *         });
 *     });
 *
 *     function addRow(rowData) {
 *         const id = String(nextId++);
 *         data.set(id, { ...rowData, id });
 *
 *         const row = document.createElement("tr");
 *         row.dataset.id = id;
 *
 *         const fields = options.columns || Object.keys(rowData);
 *         row.innerHTML = `
 *             <td><input type="checkbox" class="select-checkbox"></td>
 *             ${fields.map(f => `<td data-field="${f}">${rowData[f] || ""}</td>`).join("")}
 *             <td><button class="delete-btn">Delete</button></td>
 *         `;
 *
 *         tbody.appendChild(row);
 *         return id;
 *     }
 *
 *     function updateRow(id, newData) {
 *         const row = tbody.querySelector(`tr[data-id="${id}"]`);
 *         const rowData = data.get(id);
 *
 *         if (row && rowData) {
 *             Object.assign(rowData, newData);
 *
 *             Object.entries(newData).forEach(([field, value]) => {
 *                 const cell = row.querySelector(`td[data-field="${field}"]`);
 *                 if (cell) cell.textContent = value;
 *             });
 *
 *             return true;
 *         }
 *         return false;
 *     }
 *
 *     function deleteRow(id) {
 *         const row = tbody.querySelector(`tr[data-id="${id}"]`);
 *         if (row) {
 *             row.remove();
 *             data.delete(id);
 *             selectedRows.delete(id);
 *             return true;
 *         }
 *         return false;
 *     }
 *
 *     return {
 *         addRow,
 *         updateRow,
 *         deleteRow,
 *         getData() {
 *             return Array.from(data.values());
 *         },
 *         getSelected() {
 *             return Array.from(selectedRows);
 *         },
 *         deleteSelected() {
 *             selectedRows.forEach(id => deleteRow(id));
 *         }
 *     };
 * }
 */

// =============================================================
// Test HTML Template
// =============================================================
/*
Create an HTML file with this structure to test the exercises:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Event Delegation Exercises</title>
    <style>
        .active { background-color: #3498db; color: white; }
        .completed { text-decoration: line-through; opacity: 0.6; }
        .open > .accordion-content { max-height: 500px; }
        .accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s; }
        .tab.active { border-bottom: 2px solid #3498db; }
        .tab-panel { display: none; }
        .tab-panel.active { display: block; }
        .dropdown.open .dropdown-menu { display: block; }
        .dropdown-menu { display: none; }
        .tag { display: inline-block; background: #e0e0e0; padding: 4px 8px; margin: 2px; border-radius: 4px; }
        .tag-remove { border: none; background: none; cursor: pointer; margin-left: 4px; }
        .selected { background-color: #e3f2fd; }
    </style>
</head>
<body>
    <ul id="myList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    
    <div id="options">
        <button data-value="a">Option A</button>
        <button data-value="b">Option B</button>
        <button data-value="c">Option C</button>
    </div>
    
    <div id="cards">
        <div class="card" data-id="1">
            <div class="card-header">Card 1</div>
            <div class="card-body">Content</div>
            <div class="card-actions">
                <button class="view-btn">View</button>
                <button class="edit-btn">Edit</button>
                <button class="delete-btn">Delete</button>
            </div>
        </div>
    </div>
    
    <div id="todo-app">
        <input id="todo-input" placeholder="Add todo">
        <button id="add-todo">Add</button>
        <ul id="todo-list"></ul>
    </div>
    
    <div id="faq">
        <div class="accordion-item">
            <button class="accordion-header">Question 1</button>
            <div class="accordion-content">Answer 1</div>
        </div>
        <div class="accordion-item">
            <button class="accordion-header">Question 2</button>
            <div class="accordion-content">Answer 2</div>
        </div>
    </div>
    
    <div id="product-tabs">
        <div class="tab-list">
            <button class="tab active" data-tab="description">Description</button>
            <button class="tab" data-tab="reviews">Reviews</button>
        </div>
        <div id="description" class="tab-panel active">Description content</div>
        <div id="reviews" class="tab-panel">Reviews content</div>
    </div>
    
    <div id="country-select" class="dropdown">
        <button class="dropdown-toggle">Select Country</button>
        <div class="dropdown-menu">
            <div class="dropdown-option" data-value="us">United States</div>
            <div class="dropdown-option" data-value="uk">United Kingdom</div>
            <div class="dropdown-option" data-value="ca">Canada</div>
        </div>
    </div>
    
    <div id="tag-input">
        <div class="tags">
            <input type="text" placeholder="Add tag">
        </div>
    </div>

    <script src="exercises.js"></script>
</body>
</html>
*/

// =============================================================
// Summary
// =============================================================
console.log('='.repeat(60));
console.log('10.4 Event Delegation - Exercises Loaded');
console.log('='.repeat(60));
console.log('Exercises 1-3:   Basic delegation patterns');
console.log('Exercises 4-5:   Dynamic content and action routing');
console.log('Exercises 6-7:   Tables and accordions');
console.log('Exercises 8-10:  Tabs, dropdowns, tag inputs');
console.log('Bonus:           Full CRUD data table');
console.log('='.repeat(60));
Exercises - JavaScript Tutorial | DeepML