Skip to content

JavaScript Events

Events are the core mechanism for JavaScript to interact with users. Through events, JavaScript can respond to user actions (such as clicks, keyboard input, etc.) and other browser behaviors (such as page loading, window resizing, etc.). Mastering event handling is key to creating interactive web applications. In this chapter, we will learn about the event system in JavaScript.

What are Events

Events are specific actions or state changes that occur in a web page, such as:

  • User clicks a button
  • User enters text
  • Page finishes loading
  • Window size changes
  • Mouse movement
  • Keyboard key press

JavaScript can listen to these events and execute corresponding code when events occur.

Event Types

JavaScript supports various types of events:

1. Mouse Events

javascript
// Common mouse events
const button = document.getElementById("myButton");

button.addEventListener("click", function(event) {
    console.log("Button was clicked");
});

button.addEventListener("dblclick", function(event) {
    console.log("Button was double-clicked");
});

button.addEventListener("mousedown", function(event) {
    console.log("Mouse button pressed");
});

button.addEventListener("mouseup", function(event) {
    console.log("Mouse button released");
});

button.addEventListener("mouseover", function(event) {
    console.log("Mouse hovering over button");
});

button.addEventListener("mouseout", function(event) {
    console.log("Mouse left button");
});

button.addEventListener("mousemove", function(event) {
    console.log("Mouse moving over button");
});

2. Keyboard Events

javascript
// Common keyboard events
const input = document.getElementById("myInput");

input.addEventListener("keydown", function(event) {
    console.log("Key pressed: " + event.key);
});

input.addEventListener("keyup", function(event) {
    console.log("Key released: " + event.key);
});

input.addEventListener("keypress", function(event) {
    console.log("Key held: " + event.key);
});

// Specific key handling
input.addEventListener("keydown", function(event) {
    if (event.key === "Enter") {
        console.log("Enter key pressed");
    }
    
    if (event.ctrlKey && event.key === "s") {
        console.log("Ctrl+S pressed");
        event.preventDefault(); // Prevent default behavior
    }
});

3. Form Events

javascript
// Form-related events
const form = document.getElementById("myForm");
const input = document.getElementById("myInput");

form.addEventListener("submit", function(event) {
    console.log("Form submitted");
    event.preventDefault(); // Prevent default form submission
});

input.addEventListener("focus", function(event) {
    console.log("Input gained focus");
    event.target.style.borderColor = "blue";
});

input.addEventListener("blur", function(event) {
    console.log("Input lost focus");
    event.target.style.borderColor = "gray";
});

input.addEventListener("change", function(event) {
    console.log("Input content changed: " + event.target.value);
});

input.addEventListener("input", function(event) {
    console.log("Input content changing: " + event.target.value);
});

4. Window Events

javascript
// Window-related events
window.addEventListener("load", function(event) {
    console.log("Page loaded completely");
});

window.addEventListener("beforeunload", function(event) {
    console.log("Page about to unload");
    // Can show confirmation dialog
    event.returnValue = "Are you sure you want to leave this page?";
});

window.addEventListener("resize", function(event) {
    console.log("Window resized: " + window.innerWidth + "x" + window.innerHeight);
});

window.addEventListener("scroll", function(event) {
    console.log("Page scrolled: " + window.scrollY);
});

5. Other Events

javascript
// Document events
document.addEventListener("DOMContentLoaded", function(event) {
    console.log("DOM loaded");
});

// Drag events
const draggable = document.getElementById("draggable");

draggable.addEventListener("dragstart", function(event) {
    console.log("Drag started");
    event.dataTransfer.setData("text/plain", "Dragged data");
});

draggable.addEventListener("dragend", function(event) {
    console.log("Drag ended");
});

// Media events
const video = document.getElementById("myVideo");

video.addEventListener("play", function(event) {
    console.log("Video started playing");
});

video.addEventListener("pause", function(event) {
    console.log("Video paused");
});

video.addEventListener("ended", function(event) {
    console.log("Video playback ended");
});

Event Handling Methods

1. HTML Inline Event Handlers

html
<!-- Not recommended -->
<button onclick="handleClick()">Click me</button>
<input onfocus="handleFocus()" onblur="handleBlur()">
javascript
function handleClick() {
    console.log("Button was clicked");
}

function handleFocus() {
    console.log("Input gained focus");
}

function handleBlur() {
    console.log("Input lost focus");
}

2. DOM Property Event Handlers

javascript
const button = document.getElementById("myButton");

// Set event handler
button.onclick = function(event) {
    console.log("Button was clicked");
};

button.onmouseover = function(event) {
    console.log("Mouse hovering");
};

// Remove event handler
button.onclick = null;
javascript
const button = document.getElementById("myButton");

// Add event listener
function handleClick(event) {
    console.log("Button was clicked");
}

button.addEventListener("click", handleClick);

// Can add multiple listeners for the same event
button.addEventListener("click", function(event) {
    console.log("Second click handler");
});

// Remove event listener
button.removeEventListener("click", handleClick);

Event Object

The event handler function receives an event object parameter containing detailed information about the event:

javascript
button.addEventListener("click", function(event) {
    console.log("Event type: " + event.type);
    console.log("Target element: " + event.target);
    console.log("Current target: " + event.currentTarget);
    console.log("Event timestamp: " + event.timeStamp);
    
    // Mouse event specific properties
    if (event.type === "click") {
        console.log("Mouse position X: " + event.clientX);
        console.log("Mouse position Y: " + event.clientY);
        console.log("Button: " + event.button);
    }
    
    // Keyboard event specific properties
    if (event.type === "keydown") {
        console.log("Key: " + event.key);
        console.log("Key code: " + event.keyCode);
        console.log("Ctrl pressed: " + event.ctrlKey);
        console.log("Alt pressed: " + event.altKey);
        console.log("Shift pressed: " + event.shiftKey);
    }
});

Event Propagation

Event propagation is divided into three phases:

  1. Capturing Phase: Event propagates down from document to target element
  2. Target Phase: Event reaches the target element
  3. Bubbling Phase: Event propagates up from target element to document
html
<div id="parent">
    <button id="child">Click me</button>
</div>
javascript
const parent = document.getElementById("parent");
const child = document.getElementById("child");

// Capturing phase listener
parent.addEventListener("click", function(event) {
    console.log("Parent capturing phase");
}, true); // Third parameter true means capturing phase

// Bubbling phase listener
parent.addEventListener("click", function(event) {
    console.log("Parent bubbling phase");
}, false); // Default false means bubbling phase

child.addEventListener("click", function(event) {
    console.log("Child clicked");
});

// Output order:
// Parent capturing phase
// Child clicked
// Parent bubbling phase

Preventing Default Behavior and Propagation

Preventing Default Behavior

javascript
const link = document.getElementById("myLink");

link.addEventListener("click", function(event) {
    event.preventDefault(); // Prevent link navigation
    console.log("Link clicked, but won't navigate");
});

const form = document.getElementById("myForm");

form.addEventListener("submit", function(event) {
    event.preventDefault(); // Prevent form submission
    console.log("Form submission prevented");
});

Stopping Event Propagation

javascript
const parent = document.getElementById("parent");
const child = document.getElementById("child");

child.addEventListener("click", function(event) {
    console.log("Child clicked");
    event.stopPropagation(); // Stop event propagation
});

parent.addEventListener("click", function(event) {
    console.log("Parent clicked"); // Won't execute
});

Immediately Stop Propagation

javascript
element.addEventListener("click", function(event) {
    console.log("Element clicked");
    event.stopImmediatePropagation(); // Immediately stop propagation and sibling listeners
});

Custom Events

JavaScript allows creating and dispatching custom events:

javascript
// Create custom event
const customEvent = new CustomEvent("myCustomEvent", {
    detail: {
        message: "This is custom event data",
        timestamp: Date.now()
    },
    bubbles: true, // Whether to bubble
    cancelable: true // Whether cancelable
});

// Listen to custom event
document.addEventListener("myCustomEvent", function(event) {
    console.log("Received custom event: ", event.detail.message);
    console.log("Timestamp: ", event.detail.timestamp);
});

// Dispatch custom event
document.dispatchEvent(customEvent);

// Custom event with parameters
function createAndDispatchEvent(eventName, data) {
    const event = new CustomEvent(eventName, {
        detail: data
    });
    document.dispatchEvent(event);
}

// Usage example
createAndDispatchEvent("userLogin", {
    username: "John",
    loginTime: new Date()
});

Event Delegation

Event delegation uses event bubbling to bind event handlers to parent elements:

html
<ul id="itemList">
    <li data-id="1">Item 1</li>
    <li data-id="2">Item 2</li>
    <li data-id="3">Item 3</li>
</ul>
javascript
const itemList = document.getElementById("itemList");

// Use event delegation to handle all li element click events
itemList.addEventListener("click", function(event) {
    // Check if clicked element is li
    if (event.target.tagName === "LI") {
        const itemId = event.target.dataset.id;
        console.log("Clicked item: " + itemId);
        
        // Perform corresponding action
        handleItemClick(itemId);
    }
});

function handleItemClick(itemId) {
    console.log("Processing item click, ID: " + itemId);
}

// Dynamically add new item
function addItem(text) {
    const newItem = document.createElement("li");
    newItem.textContent = text;
    newItem.dataset.id = Date.now();
    itemList.appendChild(newItem);
    // New item automatically has click event handling capability
}

Event Performance Optimization

1. Throttling

javascript
// Throttle function
function throttle(func, delay) {
    let lastExecTime = 0;
    
    return function(...args) {
        const currentTime = Date.now();
        
        if (currentTime - lastExecTime > delay) {
            func.apply(this, args);
            lastExecTime = currentTime;
        }
    };
}

// Use throttle for scroll events
window.addEventListener("scroll", throttle(function(event) {
    console.log("Scroll event handled");
}, 100)); // Execute at most once per 100ms

2. Debouncing

javascript
// Debounce function
function debounce(func, delay) {
    let timeoutId;
    
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Use debounce for input events
const searchInput = document.getElementById("searchInput");

searchInput.addEventListener("input", debounce(function(event) {
    console.log("Executing search: " + event.target.value);
    // Perform search operation
    performSearch(event.target.value);
}, 300)); // Execute only if no new input for 300ms

3. Event Listener Management

javascript
// Event listener manager
class EventManager {
    constructor() {
        this.listeners = new Map();
    }
    
    // Add event listener
    add(element, eventType, handler, options = {}) {
        const key = this._getKey(element, eventType);
        
        if (!this.listeners.has(key)) {
            this.listeners.set(key, []);
        }
        
        element.addEventListener(eventType, handler, options);
        this.listeners.get(key).push({
            element: element,
            handler: handler,
            options: options
        });
    }
    
    // Remove event listener
    remove(element, eventType, handler) {
        const key = this._getKey(element, eventType);
        const listeners = this.listeners.get(key) || [];
        
        element.removeEventListener(eventType, handler);
        
        this.listeners.set(key, listeners.filter(listener => 
            listener.handler !== handler
        ));
    }
    
    // Remove all listeners
    removeAll() {
        for (let [key, listeners] of this.listeners) {
            listeners.forEach(listener => {
                listener.element.removeEventListener(
                    listener.eventType, 
                    listener.handler, 
                    listener.options
                );
            });
        }
        this.listeners.clear();
    }
    
    // Generate unique key
    _getKey(element, eventType) {
        return `${element.constructor.name}_${eventType}`;
    }
}

// Usage example
const eventManager = new EventManager();

const button = document.getElementById("myButton");
const input = document.getElementById("myInput");

eventManager.add(button, "click", function() {
    console.log("Button clicked");
});

eventManager.add(input, "input", function() {
    console.log("Input changed");
});

// Clean up all event listeners
// eventManager.removeAll();

Event Best Practices

1. Use Modern Event Handling

javascript
// Recommended: Use addEventListener
element.addEventListener("click", handleClick);

// Not recommended: Use inline event handlers
// <button onclick="handleClick()">Click</button>

// Not recommended: Use DOM properties
// element.onclick = handleClick;

2. Use Event Delegation Wisely

javascript
// For dynamic lists, use event delegation
const list = document.getElementById("dynamicList");

list.addEventListener("click", function(event) {
    if (event.target.classList.contains("delete-btn")) {
        // Delete button clicked
        const item = event.target.closest(".list-item");
        if (item) {
            item.remove();
        }
    }
});

3. Clean Up Event Listeners Promptly

javascript
// Clean up event listeners when component is destroyed
class Component {
    constructor() {
        this.handleClick = this.handleClick.bind(this);
        this.handleResize = this.handleResize.bind(this);
        
        this.init();
    }
    
    init() {
        this.button.addEventListener("click", this.handleClick);
        window.addEventListener("resize", this.handleResize);
    }
    
    destroy() {
        this.button.removeEventListener("click", this.handleClick);
        window.removeEventListener("resize", this.handleResize);
    }
    
    handleClick(event) {
        // Handle click
    }
    
    handleResize(event) {
        // Handle window resize
    }
}

Practical Examples

1. Modal Component

javascript
class Modal {
    constructor(modalId) {
        this.modal = document.getElementById(modalId);
        this.overlay = this.modal.querySelector(".modal-overlay");
        this.closeButton = this.modal.querySelector(".modal-close");
        
        this.init();
    }
    
    init() {
        // Bind event listeners
        this.overlay.addEventListener("click", () => this.close());
        this.closeButton.addEventListener("click", () => this.close());
        
        // ESC key to close
        document.addEventListener("keydown", (event) => {
            if (event.key === "Escape" && this.isOpen()) {
                this.close();
            }
        });
    }
    
    open() {
        this.modal.classList.add("show");
        document.body.classList.add("modal-open");
        this.dispatchCustomEvent("modal:open");
    }
    
    close() {
        this.modal.classList.remove("show");
        document.body.classList.remove("modal-open");
        this.dispatchCustomEvent("modal:close");
    }
    
    isOpen() {
        return this.modal.classList.contains("show");
    }
    
    dispatchCustomEvent(eventName) {
        const event = new CustomEvent(eventName, {
            detail: { modal: this }
        });
        this.modal.dispatchEvent(event);
    }
}

// Usage example
const modal = new Modal("myModal");

// Listen to custom events
document.getElementById("myModal").addEventListener("modal:open", function(event) {
    console.log("Modal opened");
});

document.getElementById("myModal").addEventListener("modal:close", function(event) {
    console.log("Modal closed");
});

// Open modal
document.getElementById("openModal").addEventListener("click", function() {
    modal.open();
});

2. Form Validation System

javascript
class FormValidator {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.fields = this.form.querySelectorAll("[data-validate]");
        this.errors = new Map();
        
        this.init();
    }
    
    init() {
        // Add validation events for each field
        this.fields.forEach(field => {
            const validateOn = field.dataset.validateOn || "blur";
            field.addEventListener(validateOn, () => this.validateField(field));
        });
        
        // Form submission validation
        this.form.addEventListener("submit", (event) => {
            if (!this.validateForm()) {
                event.preventDefault();
                this.showErrors();
            }
        });
    }
    
    validateField(field) {
        const rules = field.dataset.validate.split(" ");
        const value = field.value.trim();
        let isValid = true;
        let errorMessage = "";
        
        for (let rule of rules) {
            switch (rule) {
                case "required":
                    if (!value) {
                        isValid = false;
                        errorMessage = "This field is required";
                    }
                    break;
                    
                case "email":
                    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
                    if (value && !emailRegex.test(value)) {
                        isValid = false;
                        errorMessage = "Please enter a valid email address";
                    }
                    break;
                    
                case "minLength":
                    const minLength = parseInt(field.dataset.minLength) || 0;
                    if (value && value.length < minLength) {
                        isValid = false;
                        errorMessage = `Minimum ${minLength} characters required`;
                    }
                    break;
            }
            
            if (!isValid) break;
        }
        
        this.setFieldError(field, isValid ? "" : errorMessage);
        return isValid;
    }
    
    validateForm() {
        let isFormValid = true;
        
        this.fields.forEach(field => {
            if (!this.validateField(field)) {
                isFormValid = false;
            }
        });
        
        return isFormValid;
    }
    
    setFieldError(field, errorMessage) {
        const errorElement = field.parentNode.querySelector(".error-message");
        
        if (errorMessage) {
            field.classList.add("error");
            if (errorElement) {
                errorElement.textContent = errorMessage;
            }
            this.errors.set(field.name, errorMessage);
        } else {
            field.classList.remove("error");
            if (errorElement) {
                errorElement.textContent = "";
            }
            this.errors.delete(field.name);
        }
    }
    
    showErrors() {
        const errorMessages = Array.from(this.errors.values());
        if (errorMessages.length > 0) {
            alert("Please fix the following errors:\n" + errorMessages.join("\n"));
        }
    }
}

// Usage example
// const validator = new FormValidator("myForm");

Summary

Key points about JavaScript events:

  1. Event Types: Mouse events, keyboard events, form events, window events, etc.
  2. Event Handling Methods: addEventListener (recommended), DOM properties, inline handlers
  3. Event Object: Object containing detailed event information
  4. Event Propagation: Capturing phase, target phase, bubbling phase
  5. Event Control: preventDefault(), stopPropagation()
  6. Custom Events: Creating and dispatching custom events
  7. Event Delegation: Using event bubbling to handle multiple element events
  8. Performance Optimization: Throttling, debouncing, event listener management
  9. Best Practices: Clean up promptly, use event delegation wisely

Mastering the event system is fundamental for creating interactive web applications. In the next chapter, we will learn about JavaScript strings.

Content is for learning and research only.