Skip to main content

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​

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​

  1. Build a modal that closes on Escape key or outside click
  2. Create an autocomplete search with debouncing
  3. Implement a carousel with keyboard navigation
  4. Build a context menu that appears on right-click
  5. Create a sortable list with drag-and-drop
  6. Implement form validation with real-time feedback
  7. Build an image gallery with keyboard shortcuts
  8. Create a custom dropdown with keyboard support

Additional Resources​