4.6: Events & Event Handling
Learn JavaScript event handling to create interactive web experiences that respond to user actions. Master event listeners, event objects, and handle clicks, keyboard input, form submissions, and other user interactions.
1. What are Events?
Events are actions or occurrences that happen in the browser that JavaScript can respond to.
Common Events
Mouse events:
click- Element is clickeddblclick- Element is double-clickedmousedown- Mouse button is pressedmouseup- Mouse button is releasedmousemove- Mouse moves over elementmouseenter- Mouse enters elementmouseleave- Mouse leaves element
Keyboard events:
keydown- Key is pressedkeyup- Key is releasedkeypress- Key is pressed (deprecated)
Form events:
submit- Form is submittedinput- Input value changeschange- Input value changes and loses focusfocus- Element receives focusblur- Element loses focus
Document/Window events:
DOMContentLoaded- HTML is parsedload- Page and resources fully loadedresize- Window is resizedscroll- Element is scrolled
2. Adding Event Listeners
addEventListener() (Modern, Recommended)
Syntax:
element.addEventListener(eventType, handlerFunction);
Example:
<button id="btn">Click Me</button>
const btn = document.querySelector("#btn");
btn.addEventListener("click", function() {
console.log("Button clicked!");
});
// Or with arrow function
btn.addEventListener("click", () => {
console.log("Button clicked!");
});
Python comparison (conceptual):
# Python doesn't have DOM events, but similar to callbacks
def on_click():
print("Button clicked!")
# Tkinter example
button.bind("<Button-1>", on_click)
Inline Event Handlers (Avoid!)
HTML:
<!-- DON'T DO THIS -->
<button onclick="alert('Clicked')">Click</button>
Why avoid:
- Mixes JavaScript with HTML
- Hard to maintain
- Can't attach multiple handlers
- Security issues
On-event Properties (Legacy)
const btn = document.querySelector("#btn");
// Old way (avoid)
btn.onclick = function() {
console.log("Clicked");
};
// Problem: Can only have ONE handler
btn.onclick = function() {
console.log("This overwrites the previous handler");
};
Best practice: Always use addEventListener().
3. The Event Object
Event handlers receive an event object with details about the event:
btn.addEventListener("click", (event) => {
console.log(event);
});
Common Event Properties
element.addEventListener("click", (e) => {
console.log(e.type); // "click"
console.log(e.target); // Element that triggered event
console.log(e.currentTarget); // Element with listener attached
console.log(e.timeStamp); // When event occurred
console.log(e.clientX); // Mouse X coordinate (mouse events)
console.log(e.clientY); // Mouse Y coordinate
console.log(e.key); // Key pressed (keyboard events)
console.log(e.code); // Physical key code
});
event.target vs event.currentTarget
<div id="parent">
<button id="child">Click</button>
</div>
const parent = document.querySelector("#parent");
parent.addEventListener("click", (e) => {
console.log("target:", e.target); // The actual clicked element (button)
console.log("currentTarget:", e.currentTarget); // Element with listener (div)
});
4. Common Event Types
Click Events
<button id="btn">Click Me</button>
<button id="dbl-btn">Double Click</button>
const btn = document.querySelector("#btn");
const dblBtn = document.querySelector("#dbl-btn");
btn.addEventListener("click", () => {
console.log("Single click");
});
dblBtn.addEventListener("dblclick", () => {
console.log("Double click");
});
Input Events
input - Fires on every change:
<input type="text" id="search">
<p id="output"></p>
const search = document.querySelector("#search");
const output = document.querySelector("#output");
search.addEventListener("input", (e) => {
output.textContent = `You typed: ${e.target.value}`;
});
change - Fires when value changes and element loses focus:
search.addEventListener("change", (e) => {
console.log("Final value:", e.target.value);
});
Form Events
<form id="form">
<input type="text" id="username" required>
<button type="submit">Submit</button>
</form>
const form = document.querySelector("#form");
form.addEventListener("submit", (e) => {
e.preventDefault(); // Prevent page reload
const username = document.querySelector("#username").value;
console.log("Form submitted:", username);
});
Important: Always call e.preventDefault() to prevent form's default submission behavior.
Keyboard Events
document.addEventListener("keydown", (e) => {
console.log("Key pressed:", e.key);
console.log("Key code:", e.code);
// Check for specific keys
if (e.key === "Enter") {
console.log("Enter pressed");
}
if (e.key === "Escape") {
console.log("Escape pressed");
}
// Check for modifiers
if (e.ctrlKey && e.key === "s") {
e.preventDefault(); // Prevent browser save dialog
console.log("Ctrl+S pressed");
}
});
Key vs Code:
e.key: The character ("a","A","Enter")e.code: Physical key ("KeyA","Enter")
Mouse Events
<div id="box" style="width: 200px; height: 200px; background: blue;"></div>
const box = document.querySelector("#box");
box.addEventListener("mouseenter", () => {
box.style.backgroundColor = "red";
});
box.addEventListener("mouseleave", () => {
box.style.backgroundColor = "blue";
});
box.addEventListener("mousemove", (e) => {
console.log(`Mouse at: ${e.clientX}, ${e.clientY}`);
});
Focus Events
<input type="text" id="input">
const input = document.querySelector("#input");
input.addEventListener("focus", () => {
input.style.backgroundColor = "lightyellow";
});
input.addEventListener("blur", () => {
input.style.backgroundColor = "";
});
5. Event Bubbling and Capturing
Event Bubbling (Default)
Events bubble up from target to ancestors:
<div id="grandparent">
<div id="parent">
<button id="child">Click</button>
</div>
</div>
document.querySelector("#grandparent").addEventListener("click", () => {
console.log("Grandparent clicked");
});
document.querySelector("#parent").addEventListener("click", () => {
console.log("Parent clicked");
});
document.querySelector("#child").addEventListener("click", () => {
console.log("Child clicked");
});
// Click button outputs:
// Child clicked
// Parent clicked
// Grandparent clicked
Event Capturing
Events capture down from root to target (rare):
element.addEventListener("click", handler, true); // Third parameter = capture phase
Stopping Propagation
stopPropagation() - Prevent bubbling:
document.querySelector("#child").addEventListener("click", (e) => {
e.stopPropagation(); // Stops bubbling
console.log("Child clicked");
});
// Now only "Child clicked" is logged
stopImmediatePropagation() - Stop all handlers on current element:
element.addEventListener("click", (e) => {
e.stopImmediatePropagation();
console.log("First handler");
});
element.addEventListener("click", () => {
console.log("Second handler - won't run");
});
6. Event Delegation
Handle events on parent instead of individual children (efficient for dynamic content):
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<button id="add">Add Item</button>
Bad approach (listener on each item):
// Doesn't work for dynamically added items!
document.querySelectorAll("li").forEach(li => {
li.addEventListener("click", () => {
console.log(li.textContent);
});
});
Good approach (event delegation):
const list = document.querySelector("#list");
list.addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
console.log(e.target.textContent);
}
});
// Works for dynamically added items!
document.querySelector("#add").addEventListener("click", () => {
const li = document.createElement("li");
li.textContent = `Item ${list.children.length + 1}`;
list.appendChild(li);
});
Benefits:
- Single event listener instead of many
- Works for dynamically added elements
- Better performance
7. Preventing Default Behavior
preventDefault()
Prevent browser's default action:
Prevent link navigation:
<a href="https://example.com" id="link">Click (won't navigate)</a>
document.querySelector("#link").addEventListener("click", (e) => {
e.preventDefault();
console.log("Link clicked but not navigating");
});
Prevent form submission:
form.addEventListener("submit", (e) => {
e.preventDefault();
// Handle with JavaScript instead
});
Prevent context menu:
document.addEventListener("contextmenu", (e) => {
e.preventDefault();
console.log("Right-click disabled");
});
8. Removing Event Listeners
To remove a listener, you need a reference to the same function:
Wrong (won't work):
btn.addEventListener("click", () => {
console.log("Clicked");
});
btn.removeEventListener("click", () => {
console.log("Clicked"); // Different function!
});
Correct:
function handleClick() {
console.log("Clicked");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // Works!
Use case: One-time listener:
function handleClick() {
console.log("Clicked once");
btn.removeEventListener("click", handleClick);
}
btn.addEventListener("click", handleClick);
Modern shortcut:
btn.addEventListener("click", function handler() {
console.log("Clicked once");
btn.removeEventListener("click", handler);
});
// Or use { once: true } option
btn.addEventListener("click", () => {
console.log("Clicked once");
}, { once: true });
9. Practical Examples
Example 1: Toggle Dark Mode
<button id="theme-toggle">Toggle Dark Mode</button>
const btn = document.querySelector("#theme-toggle");
btn.addEventListener("click", () => {
document.body.classList.toggle("dark-mode");
if (document.body.classList.contains("dark-mode")) {
btn.textContent = "Toggle Light Mode";
} else {
btn.textContent = "Toggle Dark Mode";
}
});
Example 2: Live Search Filter
<input type="text" id="search" placeholder="Search...">
<ul id="list">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
<li>Grape</li>
</ul>
const search = document.querySelector("#search");
const items = document.querySelectorAll("#list li");
search.addEventListener("input", (e) => {
const query = e.target.value.toLowerCase();
items.forEach(item => {
const text = item.textContent.toLowerCase();
if (text.includes(query)) {
item.style.display = "";
} else {
item.style.display = "none";
}
});
});
Example 3: Tab Navigation
<div class="tabs">
<button class="tab active" data-tab="tab1">Tab 1</button>
<button class="tab" data-tab="tab2">Tab 2</button>
<button class="tab" data-tab="tab3">Tab 3</button>
</div>
<div id="tab1" class="content active">Content 1</div>
<div id="tab2" class="content">Content 2</div>
<div id="tab3" class="content">Content 3</div>
const tabs = document.querySelectorAll(".tab");
const contents = document.querySelectorAll(".content");
tabs.forEach(tab => {
tab.addEventListener("click", () => {
// Remove active from all
tabs.forEach(t => t.classList.remove("active"));
contents.forEach(c => c.classList.remove("active"));
// Add active to clicked tab
tab.classList.add("active");
const targetId = tab.dataset.tab;
document.querySelector(`#${targetId}`).classList.add("active");
});
});
Example 4: Form Validation
<form id="signup">
<input type="email" id="email" placeholder="Email">
<span id="email-error" style="color: red;"></span>
<input type="password" id="password" placeholder="Password">
<span id="password-error" style="color: red;"></span>
<button type="submit">Sign Up</button>
</form>
const form = document.querySelector("#signup");
const email = document.querySelector("#email");
const password = document.querySelector("#password");
const emailError = document.querySelector("#email-error");
const passwordError = document.querySelector("#password-error");
form.addEventListener("submit", (e) => {
e.preventDefault();
// Reset errors
emailError.textContent = "";
passwordError.textContent = "";
// Validate email
if (!email.value.includes("@")) {
emailError.textContent = "Invalid email";
return;
}
// Validate password
if (password.value.length < 8) {
passwordError.textContent = "Password must be 8+ characters";
return;
}
console.log("Form valid:", { email: email.value, password: password.value });
});
10. Practical Exercises
Exercise 4.6.1: Click Counter
Create a button that:
- Shows number of times clicked
- Changes color every 5 clicks
- Resets on double-click
Exercise 4.6.2: Keyboard Game
Build a simple game:
- Press arrow keys to move a box
- Press spacebar to change color
- Press Escape to reset position
Exercise 4.6.3: Todo List with Events
Create a todo list with:
- Add item on Enter key
- Delete item on click
- Mark complete on checkbox change
- Clear all on button click
Exercise 4.6.4: Image Gallery
Build an image gallery:
- Click thumbnail to show large version
- Keyboard arrows to navigate
- Escape to close
- Click outside to close (event delegation)
11. Knowledge Check
Question 1: What's the difference between input and change events?
Show answer
`input` fires on every change immediately. `change` fires when value changes AND element loses focus.Question 2: What is event bubbling?
Show answer
Events propagate from target element up to ancestors. Click a button → button handler → parent handlers → grandparent handlers, etc.Question 3: When should you use event delegation?
Show answer
For dynamic content (elements added after page load) and when you have many similar elements. Attach one listener to parent instead of many to children.Question 4: How do you prevent a form from submitting?
Show answer
Call `e.preventDefault()` in the submit event handler.Question 5: How do you remove an event listener?
Show answer
Use `removeEventListener()` with reference to same function used in `addEventListener()`. Arrow functions can't be removed unless stored in variable.12. Key Takeaways
- Use
addEventListener()to attach event handlers (not inline or on-event properties) - Event object contains details about the event (target, type, coordinates, key, etc.)
- Common events: click, input, submit, keydown, mouseenter, focus, blur
e.preventDefault()stops default browser behavior (form submit, link navigation)e.stopPropagation()stops event bubbling- Event bubbling: events propagate up from target to ancestors
- Event delegation: handle events on parent for better performance and dynamic content
- Use named functions to remove event listeners
- Always validate user input and prevent default form submission
13. Further Resources
Documentation:
Next Steps
Excellent! You now know how to make web pages interactive with events.
In Lesson 4.7: Asynchronous JavaScript, you'll learn to handle asynchronous operations with callbacks, promises, and async/await.
Next: Lesson 4.7 - Asynchronous JavaScript →