Introduction to DOM Events
So far, we've explored how to create, modify, and style DOM elements. Now we'll learn about DOM events—the mechanism that allows your web applications to respond to user interactions and other occurrences in the browser environment.
Events are the cornerstone of interactive web applications. They establish the connection between user actions and your code, enabling you to create responsive, dynamic interfaces.
Real-World Analogy: The Doorbell System
Think of DOM events like a doorbell system in a house:
- The doorbell button is like an element in your HTML (e.g., a button)
- The action of pressing corresponds to the event type (e.g., 'click')
- The wiring represents the event listener that connects the action to a response
- The chime sound is like your event handler function that executes when triggered
- The person who answers the door is like your JavaScript code responding to the event
Just as you might install different doorbells for different entrances, you can attach different event listeners to various elements on your page.
The DOM Event Model
Modern browsers implement the DOM Level 3 Events specification, which defines a standard model for working with events. Let's explore the key components of this model:
Key Event Concepts
- Event
- An object representing an occurrence (like a user clicking a button, a page finishing loading, etc.)
- Event Target
- The object on which the event occurred or is targeted
- Event Listener
- A function registered to be called when a specific event occurs
- Event Handler
- The function that contains the code to be executed in response to the event
- Event Propagation
- The process by which events travel through the DOM tree (capturing and bubbling phases)
- Event Object
- Contains information about the event, such as its type, target, timestamp, etc.
Basic Event Registration
There are multiple ways to register event listeners in JavaScript:
Method 1: addEventListener (Recommended)
// Select the element
const button = document.getElementById('submit-button');
// Add an event listener
button.addEventListener('click', function(event) {
console.log('Button was clicked!');
console.log('Event object:', event);
});
Method 2: Event Handler Property
// Select the element
const input = document.getElementById('username');
// Assign a function to the event handler property
input.onchange = function(event) {
console.log('Input value changed to:', this.value);
};
Method 3: Inline HTML Attribute (Not Recommended)
<button onclick="handleClick()">Click Me</button>
<script>
function handleClick() {
console.log('Button was clicked!');
}
</script>
Best Practice
addEventListener() is preferred because it:
- Allows attaching multiple listeners to the same event
- Enables event capturing (discussed later)
- Provides better separation of HTML and JavaScript
- Allows for easier removal of listeners
The Event Object
When an event occurs, the browser creates an Event object containing information about the event. This object is automatically passed to your event handler function:
document.getElementById('my-button').addEventListener('click', function(event) {
console.log('Event type:', event.type); // "click"
console.log('Target element:', event.target); // The element that was clicked
console.log('Current target:', event.currentTarget); // The element the listener is attached to
console.log('Time stamp:', event.timeStamp); // When the event occurred
console.log('Mouse coordinates:', event.clientX, event.clientY); // Mouse position
// Prevent the default browser action for this event
event.preventDefault();
// Stop the event from propagating further
event.stopPropagation();
});
Common Event Object Properties
| Property | Description | Example Value |
|---|---|---|
type |
The type of event | "click", "keydown", "submit" |
target |
The element that triggered the event | The button element that was clicked |
currentTarget |
The element the listener is attached to (may differ from target during bubbling) | The parent element with the event listener |
timeStamp |
When the event occurred (milliseconds since page load) | 1234.56 |
preventDefault() |
Method to prevent the default browser action | N/A (function) |
stopPropagation() |
Method to stop event bubbling/capturing | N/A (function) |
Event Object Type-Specific Properties
Different event types provide additional properties specific to the type of interaction:
Mouse Events
document.getElementById('image').addEventListener('mousemove', function(event) {
// Mouse coordinates relative to the viewport
console.log('Client coordinates:', event.clientX, event.clientY);
// Mouse coordinates relative to the document
console.log('Page coordinates:', event.pageX, event.pageY);
// Mouse coordinates relative to the target element
console.log('Element coordinates:', event.offsetX, event.offsetY);
// Which mouse button was pressed (0: left, 1: middle, 2: right)
console.log('Button pressed:', event.button);
// Modifier keys held during the event
console.log('Ctrl key?', event.ctrlKey);
console.log('Shift key?', event.shiftKey);
console.log('Alt key?', event.altKey);
});
Keyboard Events
document.getElementById('input-field').addEventListener('keydown', function(event) {
// The key that was pressed
console.log('Key pressed:', event.key);
// Key code (deprecated but still used in some contexts)
console.log('Key code:', event.keyCode);
// For special keys like Ctrl, Shift, Alt
console.log('Ctrl key?', event.ctrlKey);
console.log('Shift key?', event.shiftKey);
console.log('Alt key?', event.altKey);
// Prevent typing in the field for certain keys
if (event.key === '@' || event.key === '#') {
event.preventDefault();
console.log('Special character blocked');
}
});
Form Events
document.getElementById('signup-form').addEventListener('submit', function(event) {
// Prevent the form from submitting normally
event.preventDefault();
// Form data
const formData = new FormData(this);
// Log form values
for (const [name, value] of formData.entries()) {
console.log(`${name}: ${value}`);
}
// Validate and submit via AJAX instead
validateAndSubmit(formData);
});
Types of DOM Events
The DOM provides a rich set of event types to capture various user interactions and browser occurrences. Let's explore the most common types:
Mouse Events
| Event Type | Triggered When | Common Use Cases |
|---|---|---|
click |
Element is clicked | Button actions, menu selection, toggling elements |
dblclick |
Element is double-clicked | Opening items, activating edit mode |
mousedown |
Mouse button is pressed down | Drag operations, drawing tools |
mouseup |
Mouse button is released | Drag operations, drawing tools |
mousemove |
Mouse is moved over an element | Interactive visualizations, hover effects |
mouseenter |
Mouse enters an element | Tooltips, highlight effects (doesn't bubble) |
mouseleave |
Mouse leaves an element | Removing tooltips, resetting hover states (doesn't bubble) |
mouseover |
Mouse enters an element or its children | Similar to mouseenter, but bubbles to parent elements |
mouseout |
Mouse leaves an element or enters child elements | Similar to mouseleave, but bubbles to parent elements |
contextmenu |
Right mouse button is clicked | Custom context menus, alternative actions |
Example: Creating a Custom Tooltip
const tooltipTrigger = document.getElementById('info-icon');
const tooltip = document.getElementById('tooltip');
tooltipTrigger.addEventListener('mouseenter', function(event) {
// Position the tooltip near the mouse
const triggerRect = this.getBoundingClientRect();
tooltip.style.left = (triggerRect.right + 10) + 'px';
tooltip.style.top = triggerRect.top + 'px';
// Show the tooltip
tooltip.classList.add('visible');
});
tooltipTrigger.addEventListener('mouseleave', function() {
// Hide the tooltip
tooltip.classList.remove('visible');
});
Keyboard Events
| Event Type | Triggered When | Common Use Cases |
|---|---|---|
keydown |
Key is pressed down | Keyboard shortcuts, form validation, games |
keyup |
Key is released | Form validation, detecting typing completion |
keypress |
Key is pressed (character keys only; deprecated) | Detecting character input (prefer keydown now) |
Example: Keyboard Shortcuts
document.addEventListener('keydown', function(event) {
// Check for Ctrl+S (Save)
if (event.ctrlKey && event.key === 's') {
event.preventDefault(); // Prevent browser's save dialog
saveDocument();
console.log('Document saved via keyboard shortcut');
}
// Check for Esc key (Cancel)
if (event.key === 'Escape') {
closeModal();
console.log('Modal closed via Escape key');
}
});
Form Events
| Event Type | Triggered When | Common Use Cases |
|---|---|---|
submit |
Form is submitted | Form validation, AJAX submission |
reset |
Form is reset | Confirming form reset, cleanup actions |
change |
Input value changes and element loses focus | Form validation, updating dependent fields |
input |
Input value changes (immediately) | Real-time validation, character counting |
focus |
Element receives focus | Showing help text, visual highlighting |
blur |
Element loses focus | Validation, saving changes |
select |
Text is selected in input/textarea | Copy to clipboard functionality, formatting options |
Example: Real-time Character Counter
const textarea = document.getElementById('message');
const counter = document.getElementById('char-counter');
const maxLength = parseInt(textarea.getAttribute('maxlength'));
textarea.addEventListener('input', function() {
const remaining = maxLength - this.value.length;
counter.textContent = `${remaining} characters remaining`;
// Change color when getting close to the limit
if (remaining < 20) {
counter.classList.add('warning');
} else {
counter.classList.remove('warning');
}
});
Document and Window Events
| Event Type | Triggered When | Common Use Cases |
|---|---|---|
DOMContentLoaded |
HTML is loaded and DOM is constructed | Initializing UI, loading initial data |
load |
Page and all resources are fully loaded | Operations that need all resources (images, etc.) |
resize |
Window size changes | Responsive adjustments, layout recalculations |
scroll |
Document or element is scrolled | Infinite scrolling, scroll-based animations |
beforeunload |
Page is about to be unloaded | Warning about unsaved changes |
unload |
Page is being unloaded | Saving state, logging activity |
online/offline |
Browser's network connection changes | Offline mode handling, connectivity warnings |
Example: Initialization and Window Events
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM is ready, initializing app...');
initApp();
});
// Handle loading of all resources
window.addEventListener('load', function() {
console.log('All resources loaded');
document.getElementById('loading-screen').style.display = 'none';
});
// Responsive layout adjustments
let resizeTimeout;
window.addEventListener('resize', function() {
// Debounce resize events to improve performance
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
console.log('Window resized, adjusting layout...');
adjustLayout();
}, 250);
});
// Handle network connectivity changes
window.addEventListener('online', function() {
console.log('Back online');
document.getElementById('connection-status').textContent = 'Online';
document.getElementById('connection-status').className = 'status-online';
syncOfflineChanges();
});
window.addEventListener('offline', function() {
console.log('Connection lost');
document.getElementById('connection-status').textContent = 'Offline';
document.getElementById('connection-status').className = 'status-offline';
});
Media Events
| Event Type | Triggered When | Common Use Cases |
|---|---|---|
play/pause |
Media playback starts/pauses | UI updates, analytics tracking |
ended |
Media playback completes | Playing next item, showing recommendations |
timeupdate |
Current playback position changes | Updating progress bar, synchronized transcripts |
volumechange |
Volume is changed or muted | Updating volume UI, saving preferences |
loadeddata |
Media data is loaded | Showing preview, enabling playback controls |
Example: Custom Video Player Controls
const video = document.getElementById('video-player');
const playButton = document.getElementById('play-button');
const progressBar = document.getElementById('progress-bar');
const timeDisplay = document.getElementById('time-display');
// Toggle play/pause
playButton.addEventListener('click', function() {
if (video.paused) {
video.play();
playButton.textContent = 'Pause';
} else {
video.pause();
playButton.textContent = 'Play';
}
});
// Update progress bar and time display
video.addEventListener('timeupdate', function() {
// Update progress bar position
const progress = (video.currentTime / video.duration) * 100;
progressBar.style.width = progress + '%';
// Update time display
const minutes = Math.floor(video.currentTime / 60);
const seconds = Math.floor(video.currentTime % 60);
timeDisplay.textContent = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
});
// Handle video completion
video.addEventListener('ended', function() {
playButton.textContent = 'Replay';
progressBar.style.width = '100%';
});
Custom Events
In addition to the built-in events, you can create and dispatch your own custom events:
Creating and Dispatching Custom Events
// Create a custom event
const productAddedEvent = new CustomEvent('productAdded', {
detail: {
productId: 12345,
productName: 'Wireless Headphones',
price: 79.99,
quantity: 1
},
bubbles: true, // Whether the event bubbles up through the DOM
cancelable: true // Whether the event can be canceled
});
// Dispatch the event on an element
document.getElementById('shopping-cart').dispatchEvent(productAddedEvent);
// Listen for the custom event
document.getElementById('shopping-cart').addEventListener('productAdded', function(event) {
console.log('Product added to cart:', event.detail.productName);
updateCartCount(event.detail.quantity);
updateCartTotal(event.detail.price * event.detail.quantity);
});
// You can also create simpler events
const simpleEvent = new Event('refresh', {
bubbles: true,
cancelable: false
});
document.getElementById('data-container').dispatchEvent(simpleEvent);
Use Cases for Custom Events
- Component Communication: Allow separate components to communicate without tight coupling
- Application State Changes: Broadcast when important state changes occur
- Custom UI Interactions: Create specialized events for your application's unique interactions
- Plugin/Extension APIs: Allow third-party code to hook into your application's lifecycle
- Polyfills: Implement missing browser events in older browsers
Example: Component Communication
// User Profile Component
class UserProfile {
constructor() {
this.loadUserData();
// Listen for theme changes from other components
document.addEventListener('themeChanged', this.updateTheme.bind(this));
}
updateTheme(event) {
console.log('Profile updating theme to:', event.detail.theme);
this.applyTheme(event.detail.theme);
}
applyTheme(theme) {
// Apply theme-specific styles to the profile
document.getElementById('user-profile').className = `profile-card theme-${theme}`;
}
loadUserData() {
// Load user data
}
}
// Settings Component
class SettingsPanel {
constructor() {
// Set up theme selector
document.getElementById('theme-selector')
.addEventListener('change', this.themeChangeHandler.bind(this));
}
themeChangeHandler(event) {
const selectedTheme = event.target.value;
// Save the theme preference
localStorage.setItem('user-theme', selectedTheme);
// Broadcast the theme change to other components
const themeEvent = new CustomEvent('themeChanged', {
detail: {
theme: selectedTheme,
timestamp: new Date().toISOString()
},
bubbles: true
});
document.dispatchEvent(themeEvent);
}
}
// Initialize components
const profile = new UserProfile();
const settings = new SettingsPanel();
Event Performance and Best Practices
Event Delegation
Instead of attaching event listeners to many individual elements, attach one listener to a parent element and use the event.target property to determine which child element triggered the event:
Inefficient (Many Listeners):
// Adding a listener to each button
document.querySelectorAll('.product-card .add-to-cart').forEach(button => {
button.addEventListener('click', function(event) {
const productId = this.dataset.productId;
addToCart(productId);
});
});
Efficient (Event Delegation):
// One listener on the parent container
document.getElementById('product-list').addEventListener('click', function(event) {
// Check if the clicked element is an add-to-cart button
if (event.target.classList.contains('add-to-cart')) {
const productId = event.target.dataset.productId;
addToCart(productId);
}
});
Benefits of Event Delegation:
- Reduces memory usage by creating fewer event listeners
- Works for dynamically added elements without needing to attach new listeners
- Simplifies code by centralizing event handling
- Improves performance, especially for large lists or tables
Debouncing and Throttling
For events that can fire rapidly (like scroll, resize, mousemove), use debouncing or throttling to limit how often your handler function executes:
Debounce Function
// Execute the function only after the user stops triggering events for a set time
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Example: Search input that only triggers API calls after typing stops
const searchInput = document.getElementById('search-input');
const searchHandler = debounce(function(event) {
const query = event.target.value;
console.log('Searching for:', query);
searchAPI(query);
}, 500); // Wait 500ms after typing stops
searchInput.addEventListener('input', searchHandler);
Throttle Function
// Execute the function at most once per specified time period
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Example: Scroll handler that updates UI at most every 100ms
const scrollHandler = throttle(function() {
updateScrollBasedUI();
}, 100);
window.addEventListener('scroll', scrollHandler);
When to Use Each:
- Debounce: When you want to execute only after the "final" event in a series (e.g., resize completion, typing completion)
- Throttle: When you want to execute regularly during continuous events, but at a controlled rate (e.g., scrolling, dragging)
Removing Event Listeners
Always remove event listeners when they're no longer needed to prevent memory leaks:
// Store a reference to the handler function
function handleClick(event) {
console.log('Button clicked!');
// Remove the listener after first click
this.removeEventListener('click', handleClick);
this.classList.add('clicked');
}
// Add the listener
document.getElementById('one-time-button').addEventListener('click', handleClick);
// For components or elements that may be removed:
class TemporaryComponent {
constructor(elementId) {
this.element = document.getElementById(elementId);
// Store bound handlers to reference for removal later
this.clickHandler = this.handleClick.bind(this);
this.keyHandler = this.handleKeypress.bind(this);
// Add listeners
this.element.addEventListener('click', this.clickHandler);
document.addEventListener('keydown', this.keyHandler);
}
handleClick(event) {
console.log('Component clicked');
}
handleKeypress(event) {
if (event.key === 'Escape') {
this.destroy();
}
}
destroy() {
// Clean up all event listeners
this.element.removeEventListener('click', this.clickHandler);
document.removeEventListener('keydown', this.keyHandler);
// Remove from DOM
this.element.remove();
}
}
Real-World Applications
Application 1: Dynamic Form Validation
Using multiple event types to provide real-time feedback:
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('registration-form');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm-password');
// Validate email as the user types
emailInput.addEventListener('input', function() {
validateEmail(this);
});
// Mark field as visited when user leaves it
emailInput.addEventListener('blur', function() {
this.classList.add('touched');
validateEmail(this);
});
// Show password strength meter as user types
passwordInput.addEventListener('input', function() {
evaluatePasswordStrength(this);
});
// Check password match on confirmation input
confirmPasswordInput.addEventListener('input', function() {
validatePasswordMatch(this, passwordInput);
});
// Handle form submission
form.addEventListener('submit', function(event) {
// Perform final validation
let isValid = true;
isValid = validateEmail(emailInput) && isValid;
isValid = evaluatePasswordStrength(passwordInput) && isValid;
isValid = validatePasswordMatch(confirmPasswordInput, passwordInput) && isValid;
if (!isValid) {
event.preventDefault();
showFormError('Please correct the errors in the form.');
}
});
function validateEmail(input) {
const value = input.value.trim();
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailPattern.test(value);
const errorElement = input.nextElementSibling;
if (!value) {
setInvalid(input, errorElement, 'Email is required');
return false;
} else if (!isValid) {
setInvalid(input, errorElement, 'Please enter a valid email address');
return false;
} else {
setValid(input, errorElement);
return true;
}
}
function evaluatePasswordStrength(input) {
const value = input.value;
const strengthMeter = document.getElementById('password-strength');
// Reset status
input.classList.remove('valid', 'invalid');
if (!value) {
strengthMeter.textContent = '';
return false;
}
// Calculate strength score (0-100)
let score = 0;
// Length check (up to 40 points)
score += Math.min(value.length * 4, 40);
// Complexity checks
if (/[A-Z]/.test(value)) score += 10; // Uppercase
if (/[a-z]/.test(value)) score += 10; // Lowercase
if (/[0-9]/.test(value)) score += 10; // Numbers
if (/[^A-Za-z0-9]/.test(value)) score += 15; // Special characters
// Variety of characters (up to 15 points)
const uniqueChars = new Set(value.split('')).size;
score += Math.min(uniqueChars, 15);
// Determine strength level
let strengthLevel, strengthText;
if (score < 40) {
strengthLevel = 'weak';
strengthText = 'Weak';
} else if (score < 70) {
strengthLevel = 'medium';
strengthText = 'Medium';
} else {
strengthLevel = 'strong';
strengthText = 'Strong';
}
// Update UI
strengthMeter.textContent = strengthText;
strengthMeter.className = `strength-${strengthLevel}`;
// Mark as valid only if at least medium strength
if (strengthLevel === 'weak') {
input.classList.add('invalid');
return false;
} else {
input.classList.add('valid');
return true;
}
}
function validatePasswordMatch(confirmInput, passwordInput) {
const confirmValue = confirmInput.value;
const passwordValue = passwordInput.value;
const errorElement = confirmInput.nextElementSibling;
if (!confirmValue) {
setInvalid(confirmInput, errorElement, 'Please confirm your password');
return false;
} else if (confirmValue !== passwordValue) {
setInvalid(confirmInput, errorElement, 'Passwords do not match');
return false;
} else {
setValid(confirmInput, errorElement);
return true;
}
}
function setValid(input, errorElement) {
input.classList.remove('invalid');
input.classList.add('valid');
errorElement.textContent = '';
}
function setInvalid(input, errorElement, message) {
input.classList.remove('valid');
input.classList.add('invalid');
errorElement.textContent = message;
}
function showFormError(message) {
const formError = document.getElementById('form-error');
formError.textContent = message;
formError.classList.add('visible');
// Scroll to error message
formError.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
});
Application 2: Drag and Drop File Uploader
Combining multiple event types for a rich user interface:
document.addEventListener('DOMContentLoaded', function() {
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const uploadButton = document.getElementById('upload-button');
const fileList = document.getElementById('file-list');
// Selected files storage
const selectedFiles = new Set();
// Click on drop zone to open file picker
dropZone.addEventListener('click', function() {
fileInput.click();
});
// Handle file selection via input
fileInput.addEventListener('change', function() {
handleFiles(this.files);
});
// Drag and drop events
dropZone.addEventListener('dragover', function(event) {
event.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', function() {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', function(event) {
event.preventDefault();
dropZone.classList.remove('drag-over');
// Access the dropped files
const files = event.dataTransfer.files;
handleFiles(files);
});
// Handle the upload button
uploadButton.addEventListener('click', function() {
if (selectedFiles.size === 0) {
showMessage('Please select files to upload', 'error');
return;
}
uploadFiles(Array.from(selectedFiles));
});
// Process the selected files
function handleFiles(fileList) {
if (fileList.length === 0) return;
for (const file of fileList) {
// Check file type and size
if (!validateFile(file)) continue;
// Add to selected files
selectedFiles.add(file);
// Create preview
createFilePreview(file);
}
// Update upload button state
updateUploadButton();
}
function validateFile(file) {
// Check file type (allow images and PDFs)
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!allowedTypes.includes(file.type)) {
showMessage(`File type ${file.type} is not supported`, 'error');
return false;
}
// Check file size (max 5MB)
const maxSize = 5 * 1024 * 1024; // 5MB in bytes
if (file.size > maxSize) {
showMessage(`File ${file.name} is too large (max 5MB)`, 'error');
return false;
}
return true;
}
function createFilePreview(file) {
const item = document.createElement('div');
item.className = 'file-item';
item.dataset.name = file.name;
// Create preview image or icon
let preview;
if (file.type.startsWith('image/')) {
preview = document.createElement('img');
preview.className = 'file-preview';
const imageUrl = URL.createObjectURL(file);
preview.src = imageUrl;
// Clean up object URL when preview is loaded
preview.onload = () => URL.revokeObjectURL(imageUrl);
} else {
preview = document.createElement('div');
preview.className = 'file-icon pdf-icon';
}
// File info
const info = document.createElement('div');
info.className = 'file-info';
const name = document.createElement('div');
name.className = 'file-name';
name.textContent = file.name;
const size = document.createElement('div');
size.className = 'file-size';
size.textContent = formatFileSize(file.size);
info.appendChild(name);
info.appendChild(size);
// Remove button
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-file';
removeBtn.innerHTML = '×';
removeBtn.addEventListener('click', function(event) {
event.stopPropagation();
// Remove file from selected files
selectedFiles.delete(file);
// Remove preview
item.remove();
// Update upload button state
updateUploadButton();
// Clean up object URL if image
if (file.type.startsWith('image/')) {
URL.revokeObjectURL(preview.src);
}
});
// Assemble file item
item.appendChild(preview);
item.appendChild(info);
item.appendChild(removeBtn);
// Add to file list
fileList.appendChild(item);
// Show the file list
fileList.classList.remove('hidden');
}
function updateUploadButton() {
if (selectedFiles.size > 0) {
uploadButton.disabled = false;
uploadButton.textContent = `Upload ${selectedFiles.size} File${selectedFiles.size !== 1 ? 's' : ''}`;
} else {
uploadButton.disabled = true;
uploadButton.textContent = 'Upload';
}
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function showMessage(text, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = `message ${type}`;
messageEl.classList.add('visible');
// Hide after 4 seconds
setTimeout(() => {
messageEl.classList.remove('visible');
}, 4000);
}
function uploadFiles(files) {
// In a real app, you would use FormData and fetch/XMLHttpRequest
// to upload the files to a server
showMessage('Upload started...', 'info');
// Simulate upload progress
let progress = 0;
const progressBar = document.getElementById('progress-bar');
progressBar.style.width = '0%';
progressBar.parentElement.classList.add('visible');
const interval = setInterval(() => {
progress += 10;
progressBar.style.width = progress + '%';
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
progressBar.parentElement.classList.remove('visible');
showMessage('Files uploaded successfully!', 'success');
// Clear all files
selectedFiles.clear();
fileList.innerHTML = '';
updateUploadButton();
}, 500);
}
}, 300);
}
});
Practical Exercise
Create an interactive "to-do list" application that demonstrates various event types:
- Set up a form to add new tasks with fields for title, description, and due date
- Implement form validation that provides real-time feedback as the user types
- Use event delegation to handle clicks on task items (complete, edit, delete)
- Add keyboard shortcuts (e.g., press "n" to focus the new task form)
- Implement drag and drop to reorder tasks
- Create a filter system that responds to change events
- Use local storage to save tasks, triggering storage events
- Add custom events to notify when tasks are approaching their due date
- Implement debounced search functionality
This exercise will give you practice with a wide range of event types and event handling techniques in a realistic context.
Summary
- DOM events are the cornerstone of interactive web applications, connecting user actions to JavaScript code
- The DOM Event Model consists of events, event targets, event listeners, and event handlers
- The Event object provides information about an event, including its type, target, and related data
- Common event types include mouse events, keyboard events, form events, document/window events, and media events
- Custom events allow components to communicate without tight coupling
- Event delegation improves performance by attaching listeners to parent elements
- Debouncing and throttling optimize performance for frequently firing events
- Always remove event listeners when they're no longer needed to prevent memory leaks
In the next lecture, we'll explore event handlers and listeners in more depth, including advanced techniques for managing event callbacks.