HTML & JS Topic
JavaScript Events
Event Listeners: Adding Interactivity
Basic Event Listener Syntax:
javascript
// Method 1: addEventListener (recommended)
element.addEventListener('event', handlerFunction);
// Method 2: Inline HTML attributes (not recommended)
<button onclick="handleClick()">Click me</button>
// Method 3: DOM property (limited to one handler)
element.onclick = function() { /* handler */ };
Adding Event Listeners:
javascript
// Get element reference
const button = document.getElementById('myButton');
const input = document.querySelector('input[type="text"]');
const div = document.querySelector('.container');
// Add click event listener
button.addEventListener('click', function(event) {
console.log('Button clicked!', event);
});
// Add multiple event types
button.addEventListener('mouseover', handleMouseOver);
button.addEventListener('mouseout', handleMouseOut);
// Using named functions
function handleClick(event) {
console.log('Button clicked at:', event.clientX, event.clientY);
}
button.addEventListener('click', handleClick);
// Anonymous function with arrow syntax
button.addEventListener('click', (event) => {
console.log('Arrow function handler');
});
// Add event listener to multiple elements
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
btn.addEventListener('click', function() {
console.log(`${this.textContent} was clicked`);
});
});
Removing Event Listeners:
javascript
// Function must be named to remove it
function handleButtonClick(event) {
console.log('Button clicked');
// Remove after first click
event.target.removeEventListener('click', handleButtonClick);
}
const button = document.getElementById('myButton');
button.addEventListener('click', handleButtonClick);
// Remove using the same function reference
button.removeEventListener('click', handleButtonClick);
// One-time event listener
button.addEventListener('click', function handleOnce() {
console.log('This runs only once');
button.removeEventListener('click', handleOnce);
}, { once: true }); // Modern browser option
// Or with options object
button.addEventListener('click', handleClick, {
once: true, // Remove after first execution
passive: true, // Never call preventDefault()
capture: false // Use bubbling phase (default)
});
Mouse Events
Common Mouse Events:
javascript
// Click events
element.addEventListener('click', function(event) {
console.log('Single click');
});
element.addEventListener('dblclick', function(event) {
console.log('Double click');
});
element.addEventListener('contextmenu', function(event) {
event.preventDefault(); // Prevent browser context menu
console.log('Right click');
// Show custom context menu
});
// Mouse movement events
element.addEventListener('mousedown', function(event) {
console.log('Mouse button pressed down', event.button);
// 0: left, 1: middle, 2: right
});
element.addEventListener('mouseup', function(event) {
console.log('Mouse button released');
});
element.addEventListener('mousemove', function(event) {
const x = event.clientX;
const y = event.clientY;
console.log(`Mouse at: (${x}, ${y})`);
// Update cursor position display
});
element.addEventListener('mouseover', function(event) {
console.log('Mouse entered element');
element.style.backgroundColor = 'yellow';
});
element.addEventListener('mouseout', function(event) {
console.log('Mouse left element');
element.style.backgroundColor = '';
});
element.addEventListener('mouseenter', function(event) {
console.log('Mouse entered element (bubbles differently)');
});
element.addEventListener('mouseleave', function(event) {
console.log('Mouse left element (bubbles differently)');
});
Mouse Event Properties:
javascript
element.addEventListener('click', function(event) {
console.log('Event details:', {
// Mouse position relative to viewport
clientX: event.clientX,
clientY: event.clientY,
// Mouse position relative to document
pageX: event.pageX,
pageY: event.pageY,
// Mouse position relative to target element
offsetX: event.offsetX,
offsetY: event.offsetY,
// Which mouse button was pressed
button: event.button, // 0=left, 1=middle, 2=right
buttons: event.buttons, // Bitmask of all pressed buttons
// Modifier keys
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey, // Command on Mac, Windows key on Windows
// The element that triggered the event
target: event.target,
// The element the listener is attached to
currentTarget: event.currentTarget,
// Related element (for mouseover/out)
relatedTarget: event.relatedTarget
});
});
Mouse Event Examples:
javascript
// Drag and Drop implementation
let isDragging = false;
let startX, startY;
let offsetX = 0, offsetY = 0;
const draggable = document.getElementById('draggable');
draggable.addEventListener('mousedown', function(event) {
isDragging = true;
startX = event.clientX;
startY = event.clientY;
// Get current position
const rect = draggable.getBoundingClientRect();
offsetX = startX - rect.left;
offsetY = startY - rect.top;
// Add move and up listeners to document
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
event.preventDefault();
});
function onMouseMove(event) {
if (!isDragging) return;
const x = event.clientX - offsetX;
const y = event.clientY - offsetY;
draggable.style.left = `${x}px`;
draggable.style.top = `${y}px`;
draggable.style.position = 'absolute';
}
function onMouseUp(event) {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
// Color picker example
const colorPalette = document.getElementById('colorPalette');
colorPalette.addEventListener('click', function(event) {
if (event.target.classList.contains('color-swatch')) {
const color = event.target.dataset.color;
document.body.style.backgroundColor = color;
console.log(`Selected color: ${color}`);
}
});
// Hover effects with mouseenter/mouseleave
const card = document.querySelector('.card');
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-10px)';
this.style.boxShadow = '0 10px 20px rgba(0,0,0,0.2)';
this.style.transition = 'all 0.3s ease';
});
card.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
});
Keyboard Events
Keyboard Event Types:
javascript
// Triggered when key is pressed down
element.addEventListener('keydown', function(event) {
console.log('Key pressed down:', event.key);
// Good for detecting modifier keys (Ctrl, Alt, Shift)
});
// Triggered when key is released
element.addEventListener('keyup', function(event) {
console.log('Key released:', event.key);
});
// Triggered after keydown, but only for character keys
element.addEventListener('keypress', function(event) {
console.log('Key pressed (character):', event.key);
// Deprecated but still works
});
Keyboard Event Properties:
javascript
input.addEventListener('keydown', function(event) {
console.log('Keyboard event details:', {
// The actual character
key: event.key, // 'a', 'Enter', 'ArrowUp', etc.
code: event.code, // 'KeyA', 'Enter', 'ArrowUp', etc.
// Modifier keys
ctrlKey: event.ctrlKey,
shiftKey: event.ctrlKey,
altKey: event.altKey,
metaKey: event.metaKey,
// Whether key is held down
repeat: event.repeat,
// Location on keyboard
location: event.location, // 0=Standard, 1=Left, 2=Right, 3=Numpad
// Legacy properties
keyCode: event.keyCode, // Deprecated
which: event.which, // Deprecated
charCode: event.charCode // Deprecated
});
});
Keyboard Event Examples:
javascript
// Form input with Enter key support
const input = document.getElementById('searchInput');
const button = document.getElementById('searchButton');
input.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
performSearch();
}
// Keyboard shortcuts
if (event.ctrlKey && event.key === 'k') {
event.preventDefault();
input.focus();
}
});
function performSearch() {
const query = input.value.trim();
if (query) {
console.log(`Searching for: ${query}`);
// Execute search
}
}
// Arrow key navigation
const items = document.querySelectorAll('.list-item');
let selectedIndex = 0;
document.addEventListener('keydown', function(event) {
switch(event.key) {
case 'ArrowDown':
event.preventDefault();
selectItem(selectedIndex + 1);
break;
case 'ArrowUp':
event.preventDefault();
selectItem(selectedIndex - 1);
break;
case 'Home':
event.preventDefault();
selectItem(0);
break;
case 'End':
event.preventDefault();
selectItem(items.length - 1);
break;
case 'Enter':
if (items[selectedIndex]) {
items[selectedIndex].click();
}
break;
}
});
function selectItem(index) {
// Remove selection from all items
items.forEach(item => item.classList.remove('selected'));
// Ensure index is within bounds
selectedIndex = Math.max(0, Math.min(index, items.length - 1));
// Add selection to new item
if (items[selectedIndex]) {
items[selectedIndex].classList.add('selected');
items[selectedIndex].scrollIntoView({ block: 'nearest' });
}
}
// Escape key to close modal
const modal = document.getElementById('modal');
const closeButton = document.getElementById('closeModal');
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && modal.style.display === 'block') {
closeModal();
}
});
function closeModal() {
modal.style.display = 'none';
}
// Character counter with validation
const textarea = document.getElementById('message');
const counter = document.getElementById('charCounter');
const maxLength = 280;
textarea.addEventListener('keydown', function(event) {
// Allow certain keys even at max length
const allowedKeys = [
'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight',
'ArrowUp', 'ArrowDown', 'Tab', 'Home', 'End'
];
if (textarea.value.length >= maxLength &&
!allowedKeys.includes(event.key) &&
!event.ctrlKey && !event.metaKey) {
event.preventDefault();
counter.style.color = 'red';
}
});
textarea.addEventListener('keyup', function() {
const remaining = maxLength - textarea.value.length;
counter.textContent = `${remaining} characters remaining`;
counter.style.color = remaining < 20 ? 'orange' :
remaining < 0 ? 'red' : 'green';
});
Event Bubbling & Capturing
The Event Flow:
text
CAPTURING PHASE (Top → Target) window → document → body → parent → target TARGET PHASE target element BUBBLING PHASE (Target → Top) target → parent → body → document → window
Bubbling Example:
javascript
<div id="grandparent" style="padding: 50px; background: lightblue;">
Grandparent
<div id="parent" style="padding: 30px; background: lightgreen;">
Parent
<button id="child" style="padding: 10px;">
Click me (Child)
</button>
</div>
</div>
<script>
const grandparent = document.getElementById('grandparent');
const parent = document.getElementById('parent');
const child = document.getElementById('child');
// Events bubble up by default
grandparent.addEventListener('click', function() {
console.log('Grandparent clicked');
});
parent.addEventListener('click', function() {
console.log('Parent clicked');
});
child.addEventListener('click', function(event) {
console.log('Child clicked (event origin)');
// event.stopPropagation(); // Uncomment to stop bubbling
});
// Clicking the button logs:
// 1. "Child clicked (event origin)"
// 2. "Parent clicked"
// 3. "Grandparent clicked"
</script>
Capturing Example:
javascript
<div id="grandparent">Grandparent
<div id="parent">Parent
<button id="child">Child</button>
</div>
</div>
<script>
const elements = ['grandparent', 'parent', 'child'];
// Add listeners in capturing phase (true)
elements.forEach(id => {
document.getElementById(id).addEventListener('click', function() {
console.log(`${this.id} - Capturing phase`);
}, true); // true = use capturing phase
});
// Add listeners in bubbling phase (default)
elements.forEach(id => {
document.getElementById(id).addEventListener('click', function() {
console.log(`${this.id} - Bubbling phase`);
}, false); // false = use bubbling phase (default)
});
// Clicking the button logs:
// 1. "grandparent - Capturing phase"
// 2. "parent - Capturing phase"
// 3. "child - Capturing phase" (Target phase happens here too)
// 4. "child - Bubbling phase"
// 5. "parent - Bubbling phase"
// 6. "grandparent - Bubbling phase"
</script>
Controlling Event Flow:
javascript
// Stop propagation (prevent bubbling)
child.addEventListener('click', function(event) {
console.log('Child clicked - stopping propagation');
event.stopPropagation(); // Stops at current element
// event.stopImmediatePropagation(); // Also stops other handlers on same element
});
// Prevent default behavior
const link = document.getElementById('myLink');
link.addEventListener('click', function(event) {
if (!confirm('Are you sure you want to leave?')) {
event.preventDefault(); // Stop navigation
event.stopPropagation(); // Optional: stop bubbling
}
});
// Event delegation using bubbling
const list = document.getElementById('todoList');
list.addEventListener('click', function(event) {
// Check if clicked element is a delete button
if (event.target.classList.contains('delete-btn')) {
const listItem = event.target.closest('li');
listItem.remove();
console.log('Item deleted via delegation');
}
// Check if clicked element is a todo item
if (event.target.classList.contains('todo-item')) {
event.target.classList.toggle('completed');
}
});
// Add new items dynamically
function addTodoItem(text) {
const li = document.createElement('li');
li.className = 'todo-item';
li.innerHTML = `
${text}
<button class="delete-btn">Delete</button>
`;
list.appendChild(li);
// No need to add event listeners to new items!
// The parent listener handles them via bubbling
}
Event Delegation Patterns:
javascript
// Pattern 1: Check element type
document.addEventListener('click', function(event) {
if (event.target.tagName === 'BUTTON') {
console.log('Button clicked:', event.target.textContent);
}
if (event.target.tagName === 'A') {
event.preventDefault();
console.log('Link clicked:', event.target.href);
}
});
// Pattern 2: Check class name
document.addEventListener('click', function(event) {
if (event.target.classList.contains('modal-close')) {
closeModal(event.target.closest('.modal'));
}
if (event.target.classList.contains('tab')) {
activateTab(event.target.dataset.tab);
}
});
// Pattern 3: Check using matches()
document.addEventListener('click', function(event) {
if (event.target.matches('.dropdown > button')) {
toggleDropdown(event.target.nextElementSibling);
}
if (event.target.matches('.accordion .header')) {
toggleAccordion(event.target.parentElement);
}
});
// Pattern 4: Find closest matching element
document.addEventListener('click', function(event) {
const deleteBtn = event.target.closest('.delete-btn');
if (deleteBtn) {
deleteItem(deleteBtn.closest('.item'));
}
const star = event.target.closest('.star-rating');
if (star) {
setRating(star.dataset.rating);
}
});
Complete Practical Examples
Example 1: Interactive Todo List
html
<div class="todo-app">
<h2>Todo List</h2>
<div class="input-section">
<input type="text" id="todoInput" placeholder="Add a new task...">
<button id="addBtn">Add</button>
</div>
<ul id="todoList" class="todo-list">
<!-- Items will be added here -->
</ul>
<div class="stats">
<span id="totalCount">0 items total</span>
<span id="completedCount">0 completed</span>
</div>
</div>
<script>
const todoInput = document.getElementById('todoInput');
const addBtn = document.getElementById('addBtn');
const todoList = document.getElementById('todoList');
const totalCount = document.getElementById('totalCount');
const completedCount = document.getElementById('completedCount');
let todos = [];
// Add new todo
addBtn.addEventListener('click', addTodo);
todoInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
addTodo();
}
});
function addTodo() {
const text = todoInput.value.trim();
if (!text) return;
const todo = {
id: Date.now(),
text: text,
completed: false,
createdAt: new Date()
};
todos.push(todo);
renderTodos();
todoInput.value = '';
todoInput.focus();
}
// Event delegation for todo list
todoList.addEventListener('click', function(event) {
const itemId = event.target.closest('li')?.dataset.id;
if (!itemId) return;
const todo = todos.find(t => t.id == itemId);
if (event.target.classList.contains('delete-btn')) {
deleteTodo(todo.id);
}
else if (event.target.classList.contains('toggle-btn')) {
toggleTodo(todo.id);
}
else if (event.target.classList.contains('edit-btn')) {
editTodo(todo.id);
}
});
// Todo actions
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
renderTodos();
}
function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
renderTodos();
}
}
function editTodo(id) {
const todo = todos.find(t => t.id === id);
const newText = prompt('Edit task:', todo.text);
if (newText !== null) {
todo.text = newText.trim();
renderTodos();
}
}
// Render all todos
function renderTodos() {
todoList.innerHTML = '';
todos.forEach(todo => {
const li = document.createElement('li');
li.dataset.id = todo.id;
li.className = todo.completed ? 'completed' : '';
li.innerHTML = `
<span class="todo-text">${todo.text}</span>
<div class="todo-actions">
<button class="toggle-btn">
${todo.completed ? '✓' : '○'}
</button>
<button class="edit-btn">Edit</button>
<button class="delete-btn">×</button>
</div>
`;
todoList.appendChild(li);
});
// Update stats
updateStats();
}
function updateStats() {
const total = todos.length;
const completed = todos.filter(t => t.completed).length;
totalCount.textContent = `${total} item${total !== 1 ? 's' : ''} total`;
completedCount.textContent = `${completed} completed`;
}
// Initialize
renderTodos();
// Keyboard shortcuts
document.addEventListener('keydown', function(event) {
// Ctrl+Shift+L to clear all
if (event.ctrlKey && event.shiftKey && event.key === 'L') {
if (confirm('Clear all todos?')) {
todos = [];
renderTodos();
}
}
// Ctrl+F to focus input
if (event.ctrlKey && event.key === 'f') {
event.preventDefault();
todoInput.focus();
}
});
</script>
Example 2: Interactive Image Gallery
html
<div class="gallery">
<div class="gallery-main">
<img id="mainImage" src="image1.jpg" alt="Main image">
<div class="image-info">
<h3 id="imageTitle">Image Title</h3>
<p id="imageDescription">Description will appear here</p>
</div>
</div>
<div class="gallery-thumbnails" id="thumbnailContainer">
<!-- Thumbnails will be added dynamically -->
</div>
<div class="gallery-controls">
<button id="prevBtn">← Previous</button>
<button id="nextBtn">Next →</button>
<button id="zoomInBtn">Zoom In</button>
<button id="zoomOutBtn">Zoom Out</button>
<button id="fullscreenBtn">Fullscreen</button>
</div>
</div>
<script>
const images = [
{
id: 1,
src: 'image1.jpg',
title: 'Mountain Landscape',
description: 'Beautiful mountain view at sunrise'
},
{
id: 2,
src: 'image2.jpg',
title: 'Ocean Waves',
description: 'Powerful waves crashing on the shore'
},
// Add more images...
];
const mainImage = document.getElementById('mainImage');
const imageTitle = document.getElementById('imageTitle');
const imageDescription = document.getElementById('imageDescription');
const thumbnailContainer = document.getElementById('thumbnailContainer');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const fullscreenBtn = document.getElementById('fullscreenBtn');
let currentIndex = 0;
let zoomLevel = 1;
// Initialize gallery
function initGallery() {
renderThumbnails();
showImage(currentIndex);
// Keyboard navigation
document.addEventListener('keydown', handleKeydown);
// Mouse wheel for zoom
mainImage.addEventListener('wheel', handleWheel);
// Double click to zoom
mainImage.addEventListener('dblclick', toggleZoom);
}
// Render thumbnails
function renderThumbnails() {
thumbnailContainer.innerHTML = '';
images.forEach((image, index) => {
const thumbnail = document.createElement('div');
thumbnail.className = 'thumbnail';
thumbnail.dataset.index = index;
const img = document.createElement('img');
img.src = image.src;
img.alt = image.title;
thumbnail.appendChild(img);
thumbnailContainer.appendChild(thumbnail);
});
}
// Event delegation for thumbnails
thumbnailContainer.addEventListener('click', function(event) {
const thumbnail = event.target.closest('.thumbnail');
if (thumbnail) {
const index = parseInt(thumbnail.dataset.index);
showImage(index);
}
});
// Show specific image
function showImage(index) {
if (index < 0 || index >= images.length) return;
currentIndex = index;
const image = images[index];
mainImage.src = image.src;
imageTitle.textContent = image.title;
imageDescription.textContent = image.description;
// Update active thumbnail
document.querySelectorAll('.thumbnail').forEach((thumb, i) => {
thumb.classList.toggle('active', i === index);
});
// Reset zoom for new image
zoomLevel = 1;
updateZoom();
}
// Navigation
prevBtn.addEventListener('click', () => showImage(currentIndex - 1));
nextBtn.addEventListener('click', () => showImage(currentIndex + 1));
// Zoom controls
zoomInBtn.addEventListener('click', () => {
zoomLevel = Math.min(zoomLevel + 0.2, 3);
updateZoom();
});
zoomOutBtn.addEventListener('click', () => {
zoomLevel = Math.max(zoomLevel - 0.2, 0.5);
updateZoom();
});
function updateZoom() {
mainImage.style.transform = `scale(${zoomLevel})`;
mainImage.style.transition = 'transform 0.3s ease';
}
function handleWheel(event) {
event.preventDefault();
if (event.ctrlKey) {
// Ctrl + wheel for zoom
const delta = event.deltaY > 0 ? -0.1 : 0.1;
zoomLevel = Math.max(0.5, Math.min(zoomLevel + delta, 3));
updateZoom();
} else {
// Wheel for navigation
if (event.deltaY > 0) {
showImage(currentIndex + 1);
} else {
showImage(currentIndex - 1);
}
}
}
function toggleZoom() {
zoomLevel = zoomLevel === 1 ? 2 : 1;
updateZoom();
}
// Fullscreen
fullscreenBtn.addEventListener('click', toggleFullscreen);
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.log(`Error attempting to enable fullscreen: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
// Keyboard shortcuts
function handleKeydown(event) {
switch(event.key) {
case 'ArrowLeft':
event.preventDefault();
showImage(currentIndex - 1);
break;
case 'ArrowRight':
event.preventDefault();
showImage(currentIndex + 1);
break;
case 'Home':
event.preventDefault();
showImage(0);
break;
case 'End':
event.preventDefault();
showImage(images.length - 1);
break;
case '+':
case '=':
if (event.ctrlKey) {
event.preventDefault();
zoomInBtn.click();
}
break;
case '-':
if (event.ctrlKey) {
event.preventDefault();
zoomOutBtn.click();
}
break;
case 'Escape':
if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
case ' ':
event.preventDefault();
toggleZoom();
break;
}
}
// Initialize
initGallery();
</script>
Example 3: Form Validation with Events
html
<form id="signupForm" novalidate>
<h2>Create Account</h2>
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username"
minlength="3" maxlength="20" required>
<div class="error-message" id="usernameError"></div>
<div class="hint">3-20 characters, letters and numbers only</div>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<div class="error-message" id="emailError"></div>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password"
minlength="8" required>
<div class="error-message" id="passwordError"></div>
<div class="password-strength">
<div class="strength-bar"></div>
<span class="strength-text">Password strength</span>
</div>
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<div class="error-message" id="confirmPasswordError"></div>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="terms" name="terms" required>
I agree to the Terms of Service
</label>
<div class="error-message" id="termsError"></div>
</div>
<button type="submit">Sign Up</button>
<div class="form-status" id="formStatus"></div>
</form>
<script>
const form = document.getElementById('signupForm');
const usernameInput = document.getElementById('username');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirmPassword');
const termsCheckbox = document.getElementById('terms');
// Real-time validation
usernameInput.addEventListener('input', validateUsername);
usernameInput.addEventListener('blur', validateUsername);
emailInput.addEventListener('input', validateEmail);
emailInput.addEventListener('blur', validateEmail);
passwordInput.addEventListener('input', function() {
validatePassword();
updatePasswordStrength();
});
confirmPasswordInput.addEventListener('input', validateConfirmPassword);
termsCheckbox.addEventListener('change', validateTerms);
// Form submission
form.addEventListener('submit', function(event) {
event.preventDefault();
if (validateForm()) {
showStatus('Submitting form...', 'info');
// Simulate API call
setTimeout(() => {
showStatus('Account created successfully!', 'success');
form.reset();
clearValidation();
}, 1500);
}
});
// Validation functions
function validateUsername() {
const value = usernameInput.value.trim();
const errorElement = document.getElementById('usernameError');
if (!value) {
showError(usernameInput, errorElement, 'Username is required');
return false;
}
if (value.length < 3 || value.length > 20) {
showError(usernameInput, errorElement, 'Must be 3-20 characters');
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
showError(usernameInput, errorElement,
'Only letters, numbers, and underscores allowed');
return false;
}
clearError(usernameInput, errorElement);
return true;
}
function validateEmail() {
const value = emailInput.value.trim();
const errorElement = document.getElementById('emailError');
if (!value) {
showError(emailInput, errorElement, 'Email is required');
return false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
showError(emailInput, errorElement, 'Please enter a valid email');
return false;
}
clearError(emailInput, errorElement);
return true;
}
function validatePassword() {
const value = passwordInput.value;
const errorElement = document.getElementById('passwordError');
if (!value) {
showError(passwordInput, errorElement, 'Password is required');
return false;
}
if (value.length < 8) {
showError(passwordInput, errorElement,
'Password must be at least 8 characters');
return false;
}
clearError(passwordInput, errorElement);
return true;
}
function validateConfirmPassword() {
const password = passwordInput.value;
const confirmPassword = confirmPasswordInput.value;
const errorElement = document.getElementById('confirmPasswordError');
if (!confirmPassword) {
showError(confirmPasswordInput, errorElement,
'Please confirm your password');
return false;
}
if (password !== confirmPassword) {
showError(confirmPasswordInput, errorElement,
'Passwords do not match');
return false;
}
clearError(confirmPasswordInput, errorElement);
return true;
}
function validateTerms() {
const errorElement = document.getElementById('termsError');
if (!termsCheckbox.checked) {
showError(termsCheckbox, errorElement,
'You must agree to the terms');
return false;
}
clearError(termsCheckbox, errorElement);
return true;
}
function validateForm() {
const validations = [
validateUsername(),
validateEmail(),
validatePassword(),
validateConfirmPassword(),
validateTerms()
];
return validations.every(v => v === true);
}
// Password strength indicator
function updatePasswordStrength() {
const password = passwordInput.value;
const strengthBar = document.querySelector('.strength-bar');
const strengthText = document.querySelector('.strength-text');
let strength = 0;
let text = 'Very Weak';
let color = '#ff4444';
if (password.length >= 8) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^a-zA-Z0-9]/.test(password)) strength++;
switch(strength) {
case 1:
text = 'Very Weak'; color = '#ff4444'; break;
case 2:
text = 'Weak'; color = '#ff8800'; break;
case 3:
text = 'Fair'; color = '#ffbb33'; break;
case 4:
text = 'Good'; color = '#00C851'; break;
case 5:
text = 'Strong'; color = '#007E33'; break;
}
strengthBar.style.width = `${strength * 20}%`;
strengthBar.style.backgroundColor = color;
strengthText.textContent = text;
strengthText.style.color = color;
}
// Helper functions
function showError(input, errorElement, message) {
input.classList.add('invalid');
errorElement.textContent = message;
errorElement.style.display = 'block';
}
function clearError(input, errorElement) {
input.classList.remove('invalid');
errorElement.textContent = '';
errorElement.style.display = 'none';
}
function showStatus(message, type) {
const statusElement = document.getElementById('formStatus');
statusElement.textContent = message;
statusElement.className = `form-status ${type}`;
statusElement.style.display = 'block';
if (type === 'success') {
setTimeout(() => {
statusElement.style.display = 'none';
}, 3000);
}
}
function clearValidation() {
document.querySelectorAll('.error-message').forEach(el => {
el.textContent = '';
el.style.display = 'none';
});
document.querySelectorAll('input').forEach(input => {
input.classList.remove('invalid');
});
}
// Initialize
updatePasswordStrength();
// Keyboard shortcuts for form
document.addEventListener('keydown', function(event) {
// Ctrl+Enter to submit form
if (event.ctrlKey && event.key === 'Enter') {
event.preventDefault();
form.dispatchEvent(new Event('submit'));
}
// Esc to clear form
if (event.key === 'Escape') {
if (confirm('Clear all form fields?')) {
form.reset();
clearValidation();
updatePasswordStrength();
}
}
});
</script>
Event Best Practices & Patterns
1. Throttling and Debouncing
javascript
// Debounce: Wait for pause in events
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle: Limit how often function can be called
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage examples
const searchInput = document.getElementById('search');
// Debounced search - waits for typing to stop
searchInput.addEventListener('input', debounce(function() {
console.log('Searching for:', this.value);
// Make API call
}, 300));
// Throttled scroll handler
window.addEventListener('scroll', throttle(function() {
console.log('Scrolling at:', window.scrollY);
// Update UI
}, 100));
Comments (0)
No comments yet. Be the first to comment!