Learning Objectives
- Add and remove event listeners
- Understand event bubbling and capturing
- Use event delegation for performance
- Handle common events (click, input, submit)
- Apply event handling best practices
Adding Event Listeners
const button = document.querySelector('#myButton');
// Modern way - addEventListener
button.addEventListener('click', (event) => {
console.log('Button clicked!');
console.log('Event object:', event);
});
// Can add multiple listeners
function handler1() {
console.log('Handler 1');
}
function handler2() {
console.log('Handler 2');
}
button.addEventListener('click', handler1);
button.addEventListener('click', handler2);
// Remove listener
button.removeEventListener('click', handler1);
// Options
button.addEventListener('click', handler, {
once: true, // Remove after first call
passive: true, // Won't call preventDefault
capture: false // Use bubbling phase
});
Event Object
button.addEventListener('click', (event) => {
// Event properties
event.target; // Element that triggered event
event.currentTarget; // Element with listener attached
event.type; // Event type ('click')
event.timeStamp; // When event occurred
// Event methods
event.preventDefault(); // Prevent default action
event.stopPropagation(); // Stop event bubbling
event.stopImmediatePropagation(); // Stop other listeners
// Mouse event specific
event.clientX; // X coordinate
event.clientY; // Y coordinate
event.button; // Which mouse button
// Keyboard event specific
event.key; // Key pressed
event.code; // Physical key code
event.ctrlKey; // Ctrl pressed?
event.shiftKey; // Shift pressed?
});
Event Delegation
// Instead of adding listeners to many items
const list = document.querySelector('#todoList');
// Add one listener to parent
list.addEventListener('click', (event) => {
// Check if clicked element matches
if (event.target.matches('.delete-btn')) {
const item = event.target.closest('.todo-item');
item.remove();
}
if (event.target.matches('.todo-checkbox')) {
const item = event.target.closest('.todo-item');
item.classList.toggle('completed');
}
});
// Benefits:
// 1. Better performance (one listener vs many)
// 2. Works for dynamically added items
// 3. Less memory usage
Common Events
// Click events
element.addEventListener('click', handler);
element.addEventListener('dblclick', handler);
element.addEventListener('contextmenu', handler); // Right-click
// Input events
input.addEventListener('input', handler); // Every keystroke
input.addEventListener('change', handler); // After blur
input.addEventListener('focus', handler);
input.addEventListener('blur', handler);
// Form events
form.addEventListener('submit', (e) => {
e.preventDefault(); // Stop form submission
const formData = new FormData(form);
// Process form data
});
form.addEventListener('reset', handler);
// Keyboard events
document.addEventListener('keydown', (e) => {
console.log('Key pressed:', e.key);
if (e.key === 'Enter') {
console.log('Enter pressed');
}
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
console.log('Ctrl+S pressed');
}
});
document.addEventListener('keyup', handler);
document.addEventListener('keypress', handler); // Deprecated
// Mouse events
element.addEventListener('mouseenter', handler);
element.addEventListener('mouseleave', handler);
element.addEventListener('mousemove', handler);
element.addEventListener('mousedown', handler);
element.addEventListener('mouseup', handler);
// Touch events (mobile)
element.addEventListener('touchstart', handler);
element.addEventListener('touchmove', handler);
element.addEventListener('touchend', handler);
// Window events
window.addEventListener('load', handler); // Page fully loaded
window.addEventListener('resize', handler); // Window resized
window.addEventListener('scroll', handler); // Page scrolled
window.addEventListener('beforeunload', handler); // Before leaving
Event Bubbling and Capturing
// Event flow: Capturing → Target → Bubbling
// Bubbling (default) - from target up to root
document.querySelector('#child').addEventListener('click', () => {
console.log('1. Child clicked');
});
document.querySelector('#parent').addEventListener('click', () => {
console.log('2. Parent clicked');
});
document.body.addEventListener('click', () => {
console.log('3. Body clicked');
});
// Clicking child logs: 1, 2, 3
// Stop bubbling
child.addEventListener('click', (e) => {
e.stopPropagation();
console.log('Only child fires');
});
// Capturing phase - from root down to target
parent.addEventListener('click', () => {
console.log('Parent (capturing)');
}, true); // true = use capturing
Real-World Examples
Form Validation
const form = document.querySelector('#signupForm');
const email = document.querySelector('#email');
const password = document.querySelector('#password');
// Real-time validation
email.addEventListener('input', (e) => {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.target.value);
e.target.classList.toggle('invalid', !isValid);
});
password.addEventListener('input', (e) => {
const isValid = e.target.value.length >= 8;
e.target.classList.toggle('invalid', !isValid);
});
// Form submission
form.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log('Submitting:', data);
// Send to server
});
Debouncing Search
function debounce(fn, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), delay);
};
}
const searchInput = document.querySelector('#search');
const performSearch = debounce((query) => {
console.log('Searching for:', query);
// API call here
}, 300);
searchInput.addEventListener('input', (e) => {
performSearch(e.target.value);
});
Best Practices
1. Use event delegation for lists
// Good - one listener
list.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
// Handle
}
});
// Bad - many listeners
items.forEach(item => {
item.addEventListener('click', handler);
});
2. Remove listeners to prevent memory leaks
const handler = () => console.log('clicked');
element.addEventListener('click', handler);
// Later, when done
element.removeEventListener('click', handler);
3. Debounce expensive operations
window.addEventListener('resize', debounce(() => {
// Expensive layout calculations
}, 250));
Key Takeaways
- Use
addEventListenerfor modern event handling - Event delegation improves performance for lists
- Events bubble up from target to root by default
- Use
preventDefault()to stop default behavior - Use
stopPropagation()to stop event bubbling - Debounce expensive event handlers (scroll, resize, input)
- Remove event listeners when elements are removed
- Event object contains useful information about the event