Module 14: Events and Event Handling
Events are actions that happen in the browser—clicks, key presses, page loads, etc. Handling events properly is crucial for creating interactive web applications.
1. Understanding Events
1.1 What are Events?
Events are signals that something has happened in the browser. They can be triggered by:
- User actions (click, keyboard, mouse movement)
- Browser actions (page load, resize)
- Network actions (data loaded)
- Timer actions (setTimeout, setInterval)
1.2 Event Types
// Mouse events
'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'contextmenu'
// Keyboard events
'keydown', 'keyup', 'keypress'
// Form events
'submit', 'change', 'input', 'focus', 'blur', 'reset'
// Window events
'load', 'resize', 'scroll', 'beforeunload', 'unload'
// Media events
'play', 'pause', 'ended', 'volumechange', 'timeupdate'
// Drag events
'drag', 'dragstart', 'dragend', 'dragenter', 'dragleave', 'drop'
2. Event Listeners
2.1 addEventListener (Recommended)
const button = document.querySelector('#myButton');
function handleClick(event) {
console.log('Button clicked!');
console.log('Event:', event);
}
// Add event listener
button.addEventListener('click', handleClick);
// Remove event listener
button.removeEventListener('click', handleClick);
2.2 Multiple Listeners
const button = document.querySelector('#myButton');
// Can add multiple listeners
button.addEventListener('click', function() {
console.log('First handler');
});
button.addEventListener('click', function() {
console.log('Second handler');
});
// Both will execute
2.3 Event Listener Options
const element = document.querySelector('.item');
// Once: Remove after first execution
element.addEventListener('click', handleClick, { once: true });
// Capture phase
element.addEventListener('click', handleClick, { capture: true });
// Passive: Can't preventDefault
element.addEventListener('touchstart', handleTouch, { passive: true });
// All options
element.addEventListener('click', handleClick, {
once: true,
capture: false,
passive: false
});
2.4 Inline Handlers (Avoid)
<!-- ❌ Not recommended -->
<button onclick="alert('Clicked!')">Click me</button>
<button onclick="handleClick()">Click me</button>
// ❌ Not recommended
button.onclick = function() {
console.log('Clicked');
};
// Only one handler possible
button.onclick = function() {
console.log('This overwrites the previous handler');
};
Use addEventListener
Always use addEventListener instead of inline handlers or onclick properties. It's more flexible and maintainable.
3. Event Object
3.1 Event Properties
element.addEventListener('click', function(event) {
console.log('Event type:', event.type); // "click"
console.log('Target:', event.target); // Element that triggered event
console.log('Current target:', event.currentTarget); // Element listener is attached to
console.log('Timestamp:', event.timeStamp); // When event occurred
console.log('Is trusted:', event.isTrusted); // User-initiated vs script
});
3.2 Mouse Event Properties
element.addEventListener('click', function(event) {
// Position relative to viewport
console.log('Client X:', event.clientX);
console.log('Client Y:', event.clientY);
// Position relative to page
console.log('Page X:', event.pageX);
console.log('Page Y:', event.pageY);
// Position relative to screen
console.log('Screen X:', event.screenX);
console.log('Screen Y:', event.screenY);
// Mouse button
console.log('Button:', event.button); // 0=left, 1=middle, 2=right
// Modifier keys
console.log('Ctrl:', event.ctrlKey);
console.log('Shift:', event.shiftKey);
console.log('Alt:', event.altKey);
console.log('Meta:', event.metaKey); // Cmd/Windows key
});
3.3 Keyboard Event Properties
element.addEventListener('keydown', function(event) {
console.log('Key:', event.key); // "a", "Enter", "ArrowUp"
console.log('Code:', event.code); // "KeyA", "Enter", "ArrowUp"
console.log('Key code:', event.keyCode); // Deprecated
// Modifier keys
console.log('Ctrl:', event.ctrlKey);
console.log('Shift:', event.shiftKey);
console.log('Alt:', event.altKey);
// Check specific keys
if (event.key === 'Enter') {
console.log('Enter pressed');
}
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Ctrl+S pressed');
}
});
4. Event Flow
4.1 Capturing and Bubbling
<div id="outer">
<div id="middle">
<button id="inner">Click me</button>
</div>
</div>
// Event flow: Capturing → Target → Bubbling
// Capturing phase (root to target)
document.getElementById('outer').addEventListener('click', () => {
console.log('Outer (capture)');
}, true);
document.getElementById('middle').addEventListener('click', () => {
console.log('Middle (capture)');
}, true);
// Bubbling phase (target to root)
document.getElementById('inner').addEventListener('click', () => {
console.log('Inner (target)');
});
document.getElementById('middle').addEventListener('click', () => {
console.log('Middle (bubble)');
});
document.getElementById('outer').addEventListener('click', () => {
console.log('Outer (bubble)');
});
// Output when clicking button:
// Outer (capture)
// Middle (capture)
// Inner (target)
// Middle (bubble)
// Outer (bubble)
4.2 stopPropagation()
element.addEventListener('click', function(event) {
console.log('Element clicked');
event.stopPropagation(); // Stop bubbling to parent elements
});
parent.addEventListener('click', function() {
console.log('This will not run if child stopPropagation');
});
4.3 stopImmediatePropagation()
element.addEventListener('click', function(event) {
console.log('First handler');
event.stopImmediatePropagation(); // Stop all other handlers
});
element.addEventListener('click', function() {
console.log('This will not run');
});
5. preventDefault()
5.1 Preventing Default Behavior
// Prevent form submission
form.addEventListener('submit', function(event) {
event.preventDefault();
console.log('Form submission prevented');
// Custom handling
const formData = new FormData(event.target);
// ... process data
});
// Prevent link navigation
link.addEventListener('click', function(event) {
event.preventDefault();
console.log('Link click prevented');
});
// Prevent context menu
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
console.log('Right-click menu prevented');
});
5.2 Conditional preventDefault
input.addEventListener('keypress', function(event) {
// Only allow numbers
if (!/[0-9]/.test(event.key)) {
event.preventDefault();
}
});
link.addEventListener('click', function(event) {
if (!isLoggedIn) {
event.preventDefault();
showLoginPrompt();
}
});
6. Event Delegation
6.1 Why Event Delegation?
// ❌ Bad: Add listener to each item
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ Good: Single listener on parent
const container = document.querySelector('.container');
container.addEventListener('click', function(event) {
if (event.target.matches('.item')) {
handleClick(event);
}
});
6.2 Delegation Pattern
// Handle multiple element types
container.addEventListener('click', function(event) {
const target = event.target;
if (target.matches('.delete-btn')) {
deleteItem(target);
} else if (target.matches('.edit-btn')) {
editItem(target);
} else if (target.matches('.item')) {
selectItem(target);
}
});
6.3 Closest() for Nested Elements
list.addEventListener('click', function(event) {
// Find closest .item ancestor
const item = event.target.closest('.item');
if (item) {
console.log('Item clicked:', item);
}
});
Event Delegation Benefits
- Better performance (fewer listeners)
- Works with dynamically added elements
- Less memory usage
- Easier to manage
7. Common Event Patterns
7.1 Click Events
button.addEventListener('click', function(event) {
console.log('Button clicked');
});
// Double click
button.addEventListener('dblclick', function(event) {
console.log('Double clicked');
});
// Prevent double-click selection
button.addEventListener('mousedown', function(event) {
if (event.detail > 1) {
event.preventDefault();
}
});
7.2 Form Events
// Input event (fires on every change)
input.addEventListener('input', function(event) {
console.log('Current value:', event.target.value);
});
// Change event (fires on blur/enter)
select.addEventListener('change', function(event) {
console.log('Selected:', event.target.value);
});
// Submit event
form.addEventListener('submit', function(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = Object.fromEntries(formData);
console.log('Form data:', data);
});
// Focus/blur
input.addEventListener('focus', function() {
this.classList.add('focused');
});
input.addEventListener('blur', function() {
this.classList.remove('focused');
});
7.3 Keyboard Events
// Keydown (fires when key is pressed)
document.addEventListener('keydown', function(event) {
console.log('Key pressed:', event.key);
// Keyboard shortcuts
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
if (event.key === 'Escape') {
closeModal();
}
// Arrow keys
if (event.key.startsWith('Arrow')) {
handleArrowKey(event.key);
}
});
// Keyup (fires when key is released)
input.addEventListener('keyup', function(event) {
if (event.key === 'Enter') {
submitForm();
}
});
7.4 Mouse Events
// Hover effects
element.addEventListener('mouseenter', function() {
this.classList.add('hovered');
});
element.addEventListener('mouseleave', function() {
this.classList.remove('hovered');
});
// Mouse movement
document.addEventListener('mousemove', function(event) {
console.log(`Mouse at ${event.clientX}, ${event.clientY}`);
});
// Drag tracking
let isDragging = false;
element.addEventListener('mousedown', function() {
isDragging = true;
});
document.addEventListener('mousemove', function(event) {
if (isDragging) {
updatePosition(event.clientX, event.clientY);
}
});
document.addEventListener('mouseup', function() {
isDragging = false;
});
7.5 Window Events
// Page load
window.addEventListener('load', function() {
console.log('Page fully loaded');
});
// DOM ready (modern)
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM ready');
initializeApp();
});
// Window resize
window.addEventListener('resize', function() {
console.log('Window resized:', window.innerWidth, window.innerHeight);
});
// Scroll
window.addEventListener('scroll', function() {
console.log('Scroll position:', window.scrollY);
});
// Before unload (leaving page)
window.addEventListener('beforeunload', function(event) {
if (hasUnsavedChanges) {
event.preventDefault();
event.returnValue = ''; // Show confirmation dialog
}
});
8. Custom Events
8.1 Creating Custom Events
// Create custom event
const event = new CustomEvent('userLogin', {
detail: {
username: 'john_doe',
timestamp: Date.now()
},
bubbles: true,
cancelable: true
});
// Dispatch event
element.dispatchEvent(event);
// Listen for custom event
element.addEventListener('userLogin', function(event) {
console.log('User logged in:', event.detail.username);
});
8.2 Event Communication Pattern
// Publisher
class DataService {
loadData() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
const event = new CustomEvent('dataLoaded', {
detail: { data }
});
document.dispatchEvent(event);
});
}
}
// Subscriber
document.addEventListener('dataLoaded', function(event) {
console.log('Data received:', event.detail.data);
updateUI(event.detail.data);
});
9. Debouncing and Throttling
9.1 Debounce (Wait for Pause)
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage: Search as user types
const searchInput = document.querySelector('#search');
const debouncedSearch = debounce(function(event) {
console.log('Searching for:', event.target.value);
performSearch(event.target.value);
}, 300);
searchInput.addEventListener('input', debouncedSearch);
9.2 Throttle (Limit Frequency)
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage: Scroll event
const throttledScroll = throttle(function() {
console.log('Scroll position:', window.scrollY);
updateScrollIndicator();
}, 100);
window.addEventListener('scroll', throttledScroll);
Performance Optimization
Use debouncing for search/input and throttling for scroll/resize events to improve performance.
10. Practical Examples
10.1 Form Validation
const form = document.querySelector('#registrationForm');
const emailInput = document.querySelector('#email');
const passwordInput = document.querySelector('#password');
emailInput.addEventListener('blur', function() {
const email = this.value;
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (!isValid) {
this.classList.add('error');
showError(this, 'Please enter a valid email');
} else {
this.classList.remove('error');
clearError(this);
}
});
form.addEventListener('submit', function(event) {
event.preventDefault();
if (validateForm()) {
submitForm();
}
});
10.2 Drag and Drop
const draggable = document.querySelector('.draggable');
const dropzone = document.querySelector('.dropzone');
draggable.addEventListener('dragstart', function(event) {
event.dataTransfer.setData('text/plain', event.target.id);
event.target.classList.add('dragging');
});
draggable.addEventListener('dragend', function(event) {
event.target.classList.remove('dragging');
});
dropzone.addEventListener('dragover', function(event) {
event.preventDefault(); // Allow drop
this.classList.add('drag-over');
});
dropzone.addEventListener('dragleave', function() {
this.classList.remove('drag-over');
});
dropzone.addEventListener('drop', function(event) {
event.preventDefault();
this.classList.remove('drag-over');
const id = event.dataTransfer.getData('text/plain');
const element = document.getElementById(id);
this.appendChild(element);
});
10.3 Infinite Scroll
let page = 1;
let loading = false;
window.addEventListener('scroll', function() {
if (loading) return;
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.documentElement.scrollHeight - 200;
if (scrollPosition >= threshold) {
loading = true;
loadMoreContent();
}
});
async function loadMoreContent() {
page++;
const response = await fetch(`/api/items?page=${page}`);
const items = await response.json();
appendItems(items);
loading = false;
}
10.4 Keyboard Navigation
const items = document.querySelectorAll('.menu-item');
let currentIndex = 0;
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowDown') {
event.preventDefault();
currentIndex = Math.min(currentIndex + 1, items.length - 1);
focusItem(currentIndex);
} else if (event.key === 'ArrowUp') {
event.preventDefault();
currentIndex = Math.max(currentIndex - 1, 0);
focusItem(currentIndex);
} else if (event.key === 'Enter') {
items[currentIndex].click();
}
});
function focusItem(index) {
items.forEach((item, i) => {
item.classList.toggle('focused', i === index);
});
}
Summary
In this module, you learned:
- ✅ Understanding event types and the event object
- ✅ Adding and removing event listeners
- ✅ Event flow: capturing, target, and bubbling phases
- ✅ preventDefault() and stopPropagation()
- ✅ Event delegation pattern
- ✅ Common event patterns (click, form, keyboard, mouse)
- ✅ Custom events for component communication
- ✅ Performance optimization with debouncing and throttling
- ✅ Practical examples: forms, drag-drop, infinite scroll
Next Steps
In Module 15, you'll learn about Browser APIs including localStorage, fetch, and more.
Practice Exercises
- Build a modal that closes on Escape key or outside click
- Create an autocomplete search with debouncing
- Implement a carousel with keyboard navigation
- Build a context menu that appears on right-click
- Create a sortable list with drag-and-drop
- Implement form validation with real-time feedback
- Build an image gallery with keyboard shortcuts
- Create a custom dropdown with keyboard support