Menu

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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?