javascript
exercises
exercises.js⚡javascript
/**
* ============================================================
* 10.3 Events and Event Handling - Exercises
* ============================================================
*
* Practice event handling including adding listeners,
* working with the event object, and controlling propagation.
*
* 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: Add Click Handler
// =============================================================
/**
* Add a click handler to a button that logs a message
* and changes the button's text
*
* @param {string} buttonId - The ID of the button
* @param {string} message - Message to log on click
* @returns {boolean} - True if successful
*
* Example:
* addClickHandler("btn", "Button was clicked!")
* → Button click logs message and changes text to "Clicked!"
*/
function addClickHandler(buttonId, message) {
// Your code here
}
// Test
// addClickHandler("testButton", "You clicked the button!");
/*
* HINT: Use addEventListener and modify textContent on click
*
* SOLUTION:
* function addClickHandler(buttonId, message) {
* const button = document.getElementById(buttonId);
* if (!button) return false;
*
* button.addEventListener("click", () => {
* console.log(message);
* button.textContent = "Clicked!";
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 2: Toggle Handler
// =============================================================
/**
* Add a handler that toggles a class on click
* Return function to remove the handler
*
* @param {string} elementId - The ID of the element
* @param {string} className - The class to toggle
* @returns {Function} - Function to remove the handler
*
* Example:
* const remove = addToggleHandler("box", "active");
* // Click toggles "active" class
* remove(); // Removes the handler
*/
function addToggleHandler(elementId, className) {
// Your code here
}
// Test
// const removeToggle = addToggleHandler("toggleBox", "highlighted");
/*
* HINT: Store the handler function to return it for removal
*
* SOLUTION:
* function addToggleHandler(elementId, className) {
* const element = document.getElementById(elementId);
* if (!element) return () => {};
*
* function handler() {
* element.classList.toggle(className);
* }
*
* element.addEventListener("click", handler);
*
* return function() {
* element.removeEventListener("click", handler);
* };
* }
*/
// =============================================================
// EXERCISE 3: One-Time Handler
// =============================================================
/**
* Add a handler that runs only once, then removes itself
*
* @param {string} elementId - The ID of the element
* @param {string} eventType - Type of event to listen for
* @param {Function} callback - Function to run once
* @returns {boolean} - True if successful
*
* Example:
* addOneTimeHandler("btn", "click", () => console.log("First click only!"));
*/
function addOneTimeHandler(elementId, eventType, callback) {
// Your code here
}
// Test
// addOneTimeHandler("initBtn", "click", () => alert("Initialized!"));
/*
* HINT: Use the { once: true } option
*
* SOLUTION:
* function addOneTimeHandler(elementId, eventType, callback) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* element.addEventListener(eventType, callback, { once: true });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 4: Log Event Details
// =============================================================
/**
* Add a click handler that logs key event properties
* Return an object with the captured event details
*
* @param {string} elementId - The ID of the element
* @returns {Object|null} - Object that will contain last event details
*
* The returned object should track:
* - type, target tagName, timestamp, clientX, clientY
*/
function createEventLogger(elementId) {
// Your code here
}
// Test
// const logger = createEventLogger("logBox");
// // Click the element, then:
// console.log(logger);
/*
* HINT: Return an object and update it inside the handler
*
* SOLUTION:
* function createEventLogger(elementId) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* const eventData = {};
*
* element.addEventListener("click", (event) => {
* eventData.type = event.type;
* eventData.target = event.target.tagName;
* eventData.timestamp = event.timeStamp;
* eventData.clientX = event.clientX;
* eventData.clientY = event.clientY;
*
* console.log("Event logged:", eventData);
* });
*
* return eventData;
* }
*/
// =============================================================
// EXERCISE 5: Keyboard Shortcut
// =============================================================
/**
* Register a keyboard shortcut handler
*
* @param {Object} shortcut - Shortcut configuration
* @param {string} shortcut.key - Key to press
* @param {boolean} shortcut.ctrlKey - Require Ctrl?
* @param {boolean} shortcut.shiftKey - Require Shift?
* @param {Function} callback - Function to call when shortcut pressed
* @returns {Function} - Function to unregister the shortcut
*
* Example:
* const unregister = registerShortcut(
* { key: "s", ctrlKey: true },
* () => console.log("Save!")
* );
*/
function registerShortcut(shortcut, callback) {
// Your code here
}
// Test
// const unregister = registerShortcut({ key: "k", ctrlKey: true }, () => alert("Search!"));
/*
* HINT: Check all modifier conditions before calling callback
*
* SOLUTION:
* function registerShortcut(shortcut, callback) {
* function handler(event) {
* const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase();
* const ctrlMatch = !shortcut.ctrlKey || event.ctrlKey;
* const shiftMatch = !shortcut.shiftKey || event.shiftKey;
* const altMatch = !shortcut.altKey || event.altKey;
*
* if (keyMatch && ctrlMatch && shiftMatch && altMatch) {
* event.preventDefault();
* callback(event);
* }
* }
*
* document.addEventListener("keydown", handler);
*
* return function() {
* document.removeEventListener("keydown", handler);
* };
* }
*/
// =============================================================
// EXERCISE 6: Mouse Position Tracker
// =============================================================
/**
* Track mouse position within an element
* Display coordinates in a specified output element
*
* @param {string} trackerId - ID of element to track mouse in
* @param {string} outputId - ID of element to display coordinates
* @returns {Object} - { start, stop } to control tracking
*
* Example:
* const tracker = createMouseTracker("canvas", "coords");
* tracker.start(); // Begin tracking
* tracker.stop(); // Stop tracking
*/
function createMouseTracker(trackerId, outputId) {
// Your code here
}
// Test
// const tracker = createMouseTracker("trackArea", "positionDisplay");
/*
* HINT: Store handler reference to enable start/stop
*
* SOLUTION:
* function createMouseTracker(trackerId, outputId) {
* const tracker = document.getElementById(trackerId);
* const output = document.getElementById(outputId);
*
* if (!tracker || !output) return null;
*
* function handler(event) {
* const x = event.offsetX;
* const y = event.offsetY;
* output.textContent = `X: ${x}, Y: ${y}`;
* }
*
* return {
* start() {
* tracker.addEventListener("mousemove", handler);
* },
* stop() {
* tracker.removeEventListener("mousemove", handler);
* output.textContent = "";
* }
* };
* }
*/
// =============================================================
// EXERCISE 7: Stop Propagation
// =============================================================
/**
* Create a click handler that stops propagation
* Parent click handlers should not fire
*
* @param {string} childId - ID of child element
* @param {Function} childCallback - Function to run on child click
* @returns {boolean} - True if successful
*
* Example:
* createIsolatedHandler("innerBtn", () => console.log("Only this runs"));
*/
function createIsolatedHandler(childId, childCallback) {
// Your code here
}
// Test
// createIsolatedHandler("innerButton", () => console.log("Inner only!"));
/*
* HINT: Use event.stopPropagation() inside the handler
*
* SOLUTION:
* function createIsolatedHandler(childId, childCallback) {
* const child = document.getElementById(childId);
* if (!child) return false;
*
* child.addEventListener("click", (event) => {
* event.stopPropagation();
* childCallback(event);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 8: Prevent Default
// =============================================================
/**
* Create a link handler that prevents navigation
* and runs custom code instead
*
* @param {string} linkId - ID of the link element
* @param {Function} customAction - Action to run instead of navigation
* @returns {boolean} - True if successful
*
* Example:
* createCustomLink("navLink", () => showModal());
*/
function createCustomLink(linkId, customAction) {
// Your code here
}
// Test
// createCustomLink("myLink", () => console.log("Custom action instead of navigation"));
/*
* HINT: Use event.preventDefault() then call the custom action
*
* SOLUTION:
* function createCustomLink(linkId, customAction) {
* const link = document.getElementById(linkId);
* if (!link) return false;
*
* link.addEventListener("click", (event) => {
* event.preventDefault();
* customAction(event.target.href);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 9: Hover Effects
// =============================================================
/**
* Add hover effects to an element using mouseenter/mouseleave
*
* @param {string} elementId - ID of the element
* @param {Object} styles - { hover: {...}, normal: {...} } style objects
* @returns {boolean} - True if successful
*
* Example:
* addHoverEffect("card", {
* hover: { transform: "scale(1.05)", boxShadow: "0 4px 8px rgba(0,0,0,0.2)" },
* normal: { transform: "scale(1)", boxShadow: "none" }
* });
*/
function addHoverEffect(elementId, styles) {
// Your code here
}
// Test
// addHoverEffect("hoverCard", { hover: { opacity: "0.8" }, normal: { opacity: "1" } });
/*
* HINT: Apply hover styles on mouseenter, normal styles on mouseleave
*
* SOLUTION:
* function addHoverEffect(elementId, styles) {
* const element = document.getElementById(elementId);
* if (!element) return false;
*
* element.addEventListener("mouseenter", () => {
* Object.assign(element.style, styles.hover);
* });
*
* element.addEventListener("mouseleave", () => {
* Object.assign(element.style, styles.normal);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 10: Input Debounce
// =============================================================
/**
* Create a debounced input handler
* Only fires callback after user stops typing for specified delay
*
* @param {string} inputId - ID of the input element
* @param {Function} callback - Function to call with input value
* @param {number} delay - Debounce delay in milliseconds
* @returns {boolean} - True if successful
*
* Example:
* addDebouncedInput("searchBox", (value) => search(value), 300);
*/
function addDebouncedInput(inputId, callback, delay = 300) {
// Your code here
}
// Test
// addDebouncedInput("search", (val) => console.log("Searching:", val), 500);
/*
* HINT: Use setTimeout and clearTimeout for debouncing
*
* SOLUTION:
* function addDebouncedInput(inputId, callback, delay = 300) {
* const input = document.getElementById(inputId);
* if (!input) return false;
*
* let timeoutId;
*
* input.addEventListener("input", (event) => {
* clearTimeout(timeoutId);
* timeoutId = setTimeout(() => {
* callback(event.target.value);
* }, delay);
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 11: Form Submission Handler
// =============================================================
/**
* Handle form submission with validation
* Prevent default, validate fields, call callback with data
*
* @param {string} formId - ID of the form element
* @param {Function} onSubmit - Callback with form data object
* @param {Function} onError - Callback with validation errors
* @returns {boolean} - True if successful
*
* Example:
* handleFormSubmit("loginForm",
* (data) => console.log("Submit:", data),
* (errors) => console.log("Errors:", errors)
* );
*/
function handleFormSubmit(formId, onSubmit, onError) {
// Your code here
}
// Test
// handleFormSubmit("myForm", console.log, console.error);
/*
* HINT: Use FormData API and check required fields
*
* SOLUTION:
* function handleFormSubmit(formId, onSubmit, onError) {
* const form = document.getElementById(formId);
* if (!form) return false;
*
* form.addEventListener("submit", (event) => {
* event.preventDefault();
*
* const formData = new FormData(form);
* const data = Object.fromEntries(formData);
* const errors = [];
*
* // Check required fields
* const requiredFields = form.querySelectorAll("[required]");
* requiredFields.forEach(field => {
* if (!field.value.trim()) {
* errors.push(`${field.name || field.id} is required`);
* }
* });
*
* if (errors.length > 0) {
* onError(errors);
* } else {
* onSubmit(data);
* }
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 12: Click Outside Handler
// =============================================================
/**
* Add handler that fires when clicking outside an element
* Useful for closing modals/dropdowns
*
* @param {string} elementId - ID of the element
* @param {Function} callback - Function to call on outside click
* @returns {Function} - Function to remove the handler
*
* Example:
* const remove = onClickOutside("dropdown", () => closeDropdown());
*/
function onClickOutside(elementId, callback) {
// Your code here
}
// Test
// const removeOutsideHandler = onClickOutside("modal", () => modal.close());
/*
* HINT: Check if event.target is inside the element using contains()
*
* SOLUTION:
* function onClickOutside(elementId, callback) {
* const element = document.getElementById(elementId);
* if (!element) return () => {};
*
* function handler(event) {
* if (!element.contains(event.target)) {
* callback(event);
* }
* }
*
* // Use setTimeout to avoid triggering on the same click that opened it
* setTimeout(() => {
* document.addEventListener("click", handler);
* }, 0);
*
* return function() {
* document.removeEventListener("click", handler);
* };
* }
*/
// =============================================================
// EXERCISE 13: Double Click Prevention
// =============================================================
/**
* Prevent double-click issues on buttons
* Disable button after click, re-enable after action completes
*
* @param {string} buttonId - ID of the button
* @param {Function} asyncAction - Async function to run on click
* @returns {boolean} - True if successful
*
* Example:
* preventDoubleClick("submitBtn", async () => {
* await saveData();
* });
*/
function preventDoubleClick(buttonId, asyncAction) {
// Your code here
}
// Test
// preventDoubleClick("saveBtn", async () => { await delay(1000); console.log("Saved!"); });
/*
* HINT: Set disabled attribute, run action, then remove disabled
*
* SOLUTION:
* function preventDoubleClick(buttonId, asyncAction) {
* const button = document.getElementById(buttonId);
* if (!button) return false;
*
* button.addEventListener("click", async () => {
* if (button.disabled) return;
*
* button.disabled = true;
* const originalText = button.textContent;
* button.textContent = "Processing...";
*
* try {
* await asyncAction();
* } finally {
* button.disabled = false;
* button.textContent = originalText;
* }
* });
*
* return true;
* }
*/
// =============================================================
// EXERCISE 14: Custom Event Emitter
// =============================================================
/**
* Create a custom event system for an element
* Support emit, on, and off methods
*
* @param {string} elementId - ID of the element
* @returns {Object} - { emit, on, off } methods
*
* Example:
* const events = createEventEmitter("widget");
* events.on("update", (data) => console.log(data));
* events.emit("update", { value: 42 });
*/
function createEventEmitter(elementId) {
// Your code here
}
// Test
// const emitter = createEventEmitter("myWidget");
// emitter.on("change", (data) => console.log("Changed:", data));
// emitter.emit("change", { newValue: 10 });
/*
* HINT: Use CustomEvent with detail property for data
*
* SOLUTION:
* function createEventEmitter(elementId) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* const handlers = new Map();
*
* return {
* emit(eventName, data) {
* const event = new CustomEvent(eventName, {
* bubbles: true,
* detail: data
* });
* element.dispatchEvent(event);
* },
*
* on(eventName, handler) {
* const wrappedHandler = (e) => handler(e.detail);
* handlers.set(handler, wrappedHandler);
* element.addEventListener(eventName, wrappedHandler);
* },
*
* off(eventName, handler) {
* const wrappedHandler = handlers.get(handler);
* if (wrappedHandler) {
* element.removeEventListener(eventName, wrappedHandler);
* handlers.delete(handler);
* }
* }
* };
* }
*/
// =============================================================
// EXERCISE 15: Event Bus
// =============================================================
/**
* Create a global event bus for component communication
*
* @returns {Object} - { subscribe, publish, unsubscribe } methods
*
* Example:
* const bus = createEventBus();
* const id = bus.subscribe("user:login", (user) => console.log(user));
* bus.publish("user:login", { name: "John" });
* bus.unsubscribe(id);
*/
function createEventBus() {
// Your code here
}
// Test
// const bus = createEventBus();
// bus.subscribe("test", (data) => console.log("Received:", data));
// bus.publish("test", { message: "Hello!" });
/*
* HINT: Use a Map to store subscribers with unique IDs
*
* SOLUTION:
* function createEventBus() {
* const subscribers = new Map();
* let nextId = 1;
*
* return {
* subscribe(event, callback) {
* const id = nextId++;
*
* if (!subscribers.has(event)) {
* subscribers.set(event, new Map());
* }
*
* subscribers.get(event).set(id, callback);
* return id;
* },
*
* publish(event, data) {
* const eventSubscribers = subscribers.get(event);
* if (eventSubscribers) {
* eventSubscribers.forEach(callback => {
* callback(data);
* });
* }
* },
*
* unsubscribe(id) {
* subscribers.forEach(eventSubscribers => {
* eventSubscribers.delete(id);
* });
* }
* };
* }
*/
// =============================================================
// BONUS CHALLENGE: Gesture Detector
// =============================================================
/**
* Detect basic gestures (swipe, long press, double tap)
*
* @param {string} elementId - ID of the element
* @param {Object} handlers - { onSwipe, onLongPress, onDoubleTap }
* @returns {Object} - { destroy } to clean up listeners
*
* Example:
* const gesture = createGestureDetector("touchArea", {
* onSwipe: (direction) => console.log("Swiped:", direction),
* onLongPress: () => console.log("Long pressed!"),
* onDoubleTap: () => console.log("Double tapped!")
* });
*/
function createGestureDetector(elementId, handlers) {
// Your code here
}
/*
* SOLUTION:
* function createGestureDetector(elementId, handlers) {
* const element = document.getElementById(elementId);
* if (!element) return null;
*
* let startX, startY, startTime;
* let longPressTimer;
* let lastTap = 0;
*
* const SWIPE_THRESHOLD = 50;
* const LONG_PRESS_DURATION = 500;
* const DOUBLE_TAP_DELAY = 300;
*
* function handleStart(event) {
* const touch = event.touches?.[0] || event;
* startX = touch.clientX;
* startY = touch.clientY;
* startTime = Date.now();
*
* longPressTimer = setTimeout(() => {
* if (handlers.onLongPress) {
* handlers.onLongPress();
* }
* }, LONG_PRESS_DURATION);
* }
*
* function handleEnd(event) {
* clearTimeout(longPressTimer);
*
* const touch = event.changedTouches?.[0] || event;
* const deltaX = touch.clientX - startX;
* const deltaY = touch.clientY - startY;
* const duration = Date.now() - startTime;
*
* // Swipe detection
* if (duration < 300) {
* if (Math.abs(deltaX) > SWIPE_THRESHOLD && handlers.onSwipe) {
* handlers.onSwipe(deltaX > 0 ? "right" : "left");
* } else if (Math.abs(deltaY) > SWIPE_THRESHOLD && handlers.onSwipe) {
* handlers.onSwipe(deltaY > 0 ? "down" : "up");
* }
* }
*
* // Double tap detection
* const now = Date.now();
* if (now - lastTap < DOUBLE_TAP_DELAY && handlers.onDoubleTap) {
* handlers.onDoubleTap();
* lastTap = 0;
* } else {
* lastTap = now;
* }
* }
*
* function handleMove() {
* clearTimeout(longPressTimer);
* }
*
* element.addEventListener("mousedown", handleStart);
* element.addEventListener("mouseup", handleEnd);
* element.addEventListener("mousemove", handleMove);
* element.addEventListener("touchstart", handleStart);
* element.addEventListener("touchend", handleEnd);
* element.addEventListener("touchmove", handleMove);
*
* return {
* destroy() {
* element.removeEventListener("mousedown", handleStart);
* element.removeEventListener("mouseup", handleEnd);
* element.removeEventListener("mousemove", handleMove);
* element.removeEventListener("touchstart", handleStart);
* element.removeEventListener("touchend", handleEnd);
* element.removeEventListener("touchmove", handleMove);
* }
* };
* }
*/
// =============================================================
// 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 Handling Exercises</title>
<style>
.box { width: 100px; height: 100px; background: #3498db; margin: 10px; cursor: pointer; }
.highlighted { background: #e74c3c; transform: scale(1.1); }
.error { border: 2px solid red; }
#trackArea { width: 300px; height: 200px; background: #ecf0f1; }
#positionDisplay { font-family: monospace; }
.modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); }
.modal.open { display: flex; align-items: center; justify-content: center; }
</style>
</head>
<body>
<button id="testButton">Click Me</button>
<div id="toggleBox" class="box"></div>
<a id="myLink" href="https://example.com">Custom Link</a>
<div id="trackArea">Move mouse here</div>
<div id="positionDisplay"></div>
<form id="myForm">
<input name="email" type="email" required>
<input name="password" type="password" required>
<button type="submit">Submit</button>
</form>
<input id="search" type="text" placeholder="Search...">
<div id="dropdown">
<button>Toggle Dropdown</button>
<div class="dropdown-content">Dropdown content</div>
</div>
<button id="saveBtn">Save</button>
<div id="touchArea" style="width: 200px; height: 200px; background: #9b59b6; color: white;">
Gesture Area
</div>
<script src="exercises.js"></script>
</body>
</html>
*/
// =============================================================
// Summary
// =============================================================
console.log('='.repeat(60));
console.log('10.3 Events and Event Handling - Exercises Loaded');
console.log('='.repeat(60));
console.log('Exercises 1-3: Basic event listeners');
console.log('Exercises 4-6: Event object and properties');
console.log('Exercises 7-8: Propagation control');
console.log('Exercises 9-10: Mouse and input events');
console.log('Exercises 11-13: Form and button handling');
console.log('Exercises 14-15: Custom event systems');
console.log('Bonus: Gesture detection');
console.log('='.repeat(60));