Event handling and event delegation patterns in JavaScript
// Basic event listener
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
console.log('Button clicked!');
console.log('Event type:', event.type);
console.log('Target:', event.target);
});
// Arrow function syntax
button.addEventListener('click', (e) => {
console.log('Clicked with arrow function');
});
// Named function (removable)
function handleClick(event) {
console.log('Handling click');
}
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);
// Event object properties
document.addEventListener('click', (event) => {
console.log('Type:', event.type);
console.log('Target:', event.target);
console.log('CurrentTarget:', event.currentTarget);
console.log('Timestamp:', event.timeStamp);
console.log('ClientX:', event.clientX);
console.log('ClientY:', event.clientY);
});
// Prevent default behavior
const link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.preventDefault();
console.log('Link clicked but navigation prevented');
});
const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault();
console.log('Form submitted but page refresh prevented');
const formData = new FormData(event.target);
console.log(Object.fromEntries(formData));
});
// Stop propagation
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', () => {
console.log('Parent clicked');
});
child.addEventListener('click', (event) => {
console.log('Child clicked');
event.stopPropagation(); // Parent handler won't run
});
// Event delegation pattern
const list = document.getElementById('todoList');
// Instead of attaching to each item:
list.addEventListener('click', (event) => {
// Filter by target element
if (event.target.matches('.delete-btn')) {
const todoItem = event.target.closest('.todo-item');
todoItem.remove();
}
if (event.target.matches('.edit-btn')) {
const todoItem = event.target.closest('.todo-item');
editTodo(todoItem);
}
if (event.target.matches('.complete-checkbox')) {
const todoItem = event.target.closest('.todo-item');
todoItem.classList.toggle('completed');
}
});
// Keyboard events
const input = document.getElementById('searchInput');
input.addEventListener('keydown', (event) => {
console.log('Key:', event.key);
console.log('Code:', event.code);
console.log('Ctrl:', event.ctrlKey);
console.log('Shift:', event.shiftKey);
console.log('Alt:', event.altKey);
// Specific key handling
if (event.key === 'Enter') {
console.log('Enter pressed');
performSearch(event.target.value);
}
if (event.key === 'Escape') {
event.target.value = '';
}
// Keyboard shortcuts
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
});
input.addEventListener('keyup', (event) => {
console.log('Key released:', event.key);
});
input.addEventListener('input', (event) => {
console.log('Input value:', event.target.value);
// Fires on every character change
});
// Mouse events
const box = document.getElementById('box');
box.addEventListener('mouseenter', () => {
console.log('Mouse entered (doesn\'t bubble)');
});
box.addEventListener('mouseleave', () => {
console.log('Mouse left (doesn\'t bubble)');
});
box.addEventListener('mouseover', () => {
console.log('Mouse over (bubbles)');
});
box.addEventListener('mouseout', () => {
console.log('Mouse out (bubbles)');
});
box.addEventListener('mousemove', (event) => {
console.log(`Position: ${event.clientX}, ${event.clientY}`);
});
box.addEventListener('mousedown', () => console.log('Mouse down'));
box.addEventListener('mouseup', () => console.log('Mouse up'));
// Focus events
const field = document.getElementById('emailField');
field.addEventListener('focus', () => {
console.log('Field focused');
field.classList.add('focused');
});
field.addEventListener('blur', () => {
console.log('Field blurred');
field.classList.remove('focused');
});
// Change event
const select = document.getElementById('category');
select.addEventListener('change', (event) => {
console.log('Selected:', event.target.value);
});
// Event listener options
button.addEventListener('click', handler, {
once: true, // Remove after first invocation
passive: true, // Won't call preventDefault()
capture: false // Use bubbling phase (default)
});
// Capture vs Bubble
parent.addEventListener('click', () => {
console.log('Parent - Capture phase');
}, true); // Capture
parent.addEventListener('click', () => {
console.log('Parent - Bubble phase');
}, false); // Bubble
child.addEventListener('click', () => {
console.log('Child - Target phase');
});
// Custom events
const myEvent = new CustomEvent('userLogin', {
detail: {
username: 'alice',
timestamp: Date.now()
},
bubbles: true,
cancelable: true
});
document.addEventListener('userLogin', (event) => {
console.log('User logged in:', event.detail);
});
document.dispatchEvent(myEvent);
// Debouncing events
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((value) => {
console.log('Searching for:', value);
performSearch(value);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// Throttling events
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 200);
window.addEventListener('scroll', throttledScroll);
// Touch events (mobile)
const touchArea = document.getElementById('touchArea');
touchArea.addEventListener('touchstart', (event) => {
console.log('Touch started');
console.log('Touches:', event.touches.length);
});
touchArea.addEventListener('touchmove', (event) => {
event.preventDefault(); // Prevent scrolling
const touch = event.touches[0];
console.log(`Touch at: ${touch.clientX}, ${touch.clientY}`);
});
touchArea.addEventListener('touchend', () => {
console.log('Touch ended');
});
// Drag and drop
const draggable = document.getElementById('draggable');
const dropZone = document.getElementById('dropZone');
draggable.addEventListener('dragstart', (event) => {
event.dataTransfer.setData('text/plain', event.target.id);
event.target.style.opacity = '0.5';
});
draggable.addEventListener('dragend', (event) => {
event.target.style.opacity = '1';
});
dropZone.addEventListener('dragover', (event) => {
event.preventDefault(); // Allow drop
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (event) => {
event.preventDefault();
const id = event.dataTransfer.getData('text/plain');
const element = document.getElementById(id);
dropZone.appendChild(element);
dropZone.classList.remove('drag-over');
});
// Window events
window.addEventListener('resize', () => {
console.log(`Window size: ${window.innerWidth}x${window.innerHeight}`);
});
window.addEventListener('load', () => {
console.log('Page fully loaded');
});
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM ready');
});
window.addEventListener('beforeunload', (event) => {
event.preventDefault();
event.returnValue = ''; // Show confirmation dialog
});
// Cleanup pattern
class Component {
constructor(element) {
this.element = element;
this.handleClick = this.handleClick.bind(this);
this.element.addEventListener('click', this.handleClick);
}
handleClick(event) {
console.log('Component clicked');
}
destroy() {
this.element.removeEventListener('click', this.handleClick);
}
}
const component = new Component(button);
// Later: component.destroy();
Event listeners attach handlers to DOM elements using addEventListener(). I specify event type like click, input, submit with handler function. The event object contains information about the triggered event. Using event.preventDefault() stops default browser behavior. The event.stopPropagation() prevents event bubbling to parent elements. Event delegation attaches listeners to parent elements and filters by event.target. This pattern improves performance for many similar elements. The event.currentTarget refers to element with listener while target is actual clicked element. I use removeEventListener() to clean up and prevent memory leaks. Modern options like { once: true, passive: true, capture: false } control behavior. Understanding event flow (capture, target, bubble) is crucial.