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
// 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
// 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
// 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
// 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
// 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
<!-- Not recommended -->
<button onclick="handleClick()">Click me</button>
<input onfocus="handleFocus()" onblur="handleBlur()">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
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;3. addEventListener (Recommended)
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:
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:
- Capturing Phase: Event propagates down from document to target element
- Target Phase: Event reaches the target element
- Bubbling Phase: Event propagates up from target element to document
<div id="parent">
<button id="child">Click me</button>
</div>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 phasePreventing Default Behavior and Propagation
Preventing Default Behavior
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
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
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:
// 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:
<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>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
// 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 100ms2. Debouncing
// 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 300ms3. Event Listener Management
// 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
// 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
// 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
// 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
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
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:
- Event Types: Mouse events, keyboard events, form events, window events, etc.
- Event Handling Methods: addEventListener (recommended), DOM properties, inline handlers
- Event Object: Object containing detailed event information
- Event Propagation: Capturing phase, target phase, bubbling phase
- Event Control: preventDefault(), stopPropagation()
- Custom Events: Creating and dispatching custom events
- Event Delegation: Using event bubbling to handle multiple element events
- Performance Optimization: Throttling, debouncing, event listener management
- 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.