Events: addEventListener & the Event Object
What Are Events in JavaScript?
Events are signals that something has happened in the browser. When a user clicks a button, submits a form, scrolls the page, presses a key, or even when the page finishes loading -- each of these actions fires an event. JavaScript gives you the ability to listen for these events and run code in response. This is the foundation of all interactive web applications. Without events, web pages would be static documents with no way to respond to user actions.
The event system in JavaScript follows a simple pattern: you select an element, you tell it which event to listen for, and you provide a function that runs when that event occurs. This function is called an event handler or event listener. The modern and recommended way to set up event handling is through the addEventListener method.
The addEventListener Syntax
The addEventListener method is the standard way to attach event handlers to DOM elements. It accepts two required arguments and one optional argument. The first argument is the event type as a string. The second argument is the callback function that runs when the event fires. The optional third argument is an options object or a boolean for capture mode.
Example: Basic addEventListener Syntax
<button id="myBtn">Click Me</button>
<script>
const button = document.getElementById('myBtn');
// Syntax: element.addEventListener(eventType, handlerFunction, options)
button.addEventListener('click', function() {
console.log('Button was clicked!');
});
// You can also use a named function
function handleClick() {
console.log('Button was clicked with a named function!');
}
button.addEventListener('click', handleClick);
// Arrow function syntax
button.addEventListener('click', () => {
console.log('Button was clicked with an arrow function!');
});
</script>
addEventListener over older approaches.Event Handler Patterns: Inline vs addEventListener
There are three ways to handle events in JavaScript, but only one is recommended for modern development. Understanding all three helps you read legacy code and understand why addEventListener is the standard approach.
Example: Three Ways to Handle Events
<!-- Method 1: Inline HTML attribute (NOT recommended) -->
<button onclick="alert('Clicked!')">Inline Handler</button>
<!-- Method 2: DOM property (limited) -->
<button id="btn2">Property Handler</button>
<!-- Method 3: addEventListener (RECOMMENDED) -->
<button id="btn3">addEventListener</button>
<script>
// Method 2: DOM property -- only ONE handler per event type
const btn2 = document.getElementById('btn2');
btn2.onclick = function() {
console.log('First handler');
};
// This REPLACES the first handler!
btn2.onclick = function() {
console.log('Second handler -- first is gone!');
};
// Method 3: addEventListener -- MULTIPLE handlers per event type
const btn3 = document.getElementById('btn3');
btn3.addEventListener('click', function() {
console.log('First handler');
});
// This ADDS a second handler -- both will fire!
btn3.addEventListener('click', function() {
console.log('Second handler -- first still works!');
});
</script>
onclick="..." mixes JavaScript with HTML, making code harder to maintain, debug, and secure. It also limits you to a single handler expression. Always use addEventListener in your JavaScript code instead. The only exception is when you are reading or maintaining very old code that already uses inline handlers.Common Event Types
JavaScript supports dozens of event types. Here are the most commonly used ones, grouped by category. Understanding these event types is essential for building interactive applications.
Mouse Events
Example: Mouse Events
<div id="box" style="width:200px;height:200px;background:#eee;padding:20px;">
Hover and click me
</div>
<script>
const box = document.getElementById('box');
// click -- fires when the element is clicked
box.addEventListener('click', function() {
console.log('clicked');
});
// dblclick -- fires on double click
box.addEventListener('dblclick', function() {
console.log('double clicked');
});
// mouseenter -- fires when mouse enters the element (does not bubble)
box.addEventListener('mouseenter', function() {
box.style.background = '#cde';
});
// mouseleave -- fires when mouse leaves the element (does not bubble)
box.addEventListener('mouseleave', function() {
box.style.background = '#eee';
});
// mousemove -- fires continuously as mouse moves over the element
box.addEventListener('mousemove', function(event) {
console.log('Mouse at: ' + event.clientX + ', ' + event.clientY);
});
</script>
Keyboard Events
Example: Keyboard Events
<input type="text" id="searchInput" placeholder="Type something...">
<script>
const input = document.getElementById('searchInput');
// keydown -- fires when a key is pressed down
input.addEventListener('keydown', function(event) {
console.log('Key down: ' + event.key);
});
// keyup -- fires when a key is released
input.addEventListener('keyup', function(event) {
console.log('Key up: ' + event.key);
console.log('Current value: ' + input.value);
});
// Listen for specific keys
input.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
console.log('Enter pressed! Search for: ' + input.value);
}
if (event.key === 'Escape') {
input.value = '';
input.blur();
}
});
</script>
Form Events
Example: Form Events
<form id="myForm">
<input type="text" id="username" placeholder="Username">
<select id="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<input type="checkbox" id="agree"> I agree
<button type="submit">Submit</button>
</form>
<script>
const form = document.getElementById('myForm');
const username = document.getElementById('username');
const role = document.getElementById('role');
const agree = document.getElementById('agree');
// submit -- fires when form is submitted
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent page reload
console.log('Form submitted!');
console.log('Username: ' + username.value);
console.log('Role: ' + role.value);
console.log('Agreed: ' + agree.checked);
});
// input -- fires on every keystroke in text inputs
username.addEventListener('input', function(event) {
console.log('Typing: ' + event.target.value);
});
// change -- fires when value changes AND element loses focus
role.addEventListener('change', function(event) {
console.log('Role changed to: ' + event.target.value);
});
// change on checkbox -- fires when checked/unchecked
agree.addEventListener('change', function(event) {
console.log('Agreed: ' + event.target.checked);
});
// focus and blur -- fires when element gains/loses focus
username.addEventListener('focus', function() {
username.style.borderColor = 'blue';
});
username.addEventListener('blur', function() {
username.style.borderColor = '';
});
</script>
input event fires on every keystroke, paste, or any value change, making it ideal for real-time validation and search-as-you-type features. The change event fires only when the user finishes editing and moves away from the field (or toggles a checkbox/radio button). Choose the right event based on when you need the feedback to happen.Document and Window Events
Example: Document and Window Events
<script>
// DOMContentLoaded -- fires when HTML is parsed (before images/CSS finish loading)
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM is ready!');
// Safe to query and manipulate DOM elements here
const heading = document.querySelector('h1');
if (heading) {
heading.textContent = 'Page Loaded!';
}
});
// load -- fires when ALL resources (images, CSS, iframes) are fully loaded
window.addEventListener('load', function() {
console.log('Everything is fully loaded!');
});
// resize -- fires when the browser window is resized
window.addEventListener('resize', function() {
console.log('Window size: ' + window.innerWidth + 'x' + window.innerHeight);
});
// scroll -- fires when the page is scrolled
window.addEventListener('scroll', function() {
console.log('Scroll position: ' + window.scrollY);
});
// beforeunload -- fires before the user leaves the page
window.addEventListener('beforeunload', function(event) {
// Show a confirmation dialog (browser controls the message)
event.preventDefault();
});
</script>
DOMContentLoaded and load is important. DOMContentLoaded fires as soon as the HTML document has been fully parsed, which happens before images, stylesheets, and subframes finish loading. This makes it the ideal event for initializing your JavaScript. The load event waits for everything to finish, which can be much slower on pages with many images.The Event Object
Every time an event fires, JavaScript automatically creates an Event object and passes it to your handler function. This object contains detailed information about the event: what type it was, which element triggered it, the mouse position, which key was pressed, and much more. Accessing this object is as simple as adding a parameter to your handler function.
Example: Accessing the Event Object
<button id="infoBtn">Click for Event Info</button>
<script>
const infoBtn = document.getElementById('infoBtn');
infoBtn.addEventListener('click', function(event) {
// The event parameter is automatically provided
console.log('Event type: ' + event.type); // "click"
console.log('Timestamp: ' + event.timeStamp); // milliseconds since page load
console.log('Is trusted: ' + event.isTrusted); // true (user action) or false (script)
console.log('Button used: ' + event.button); // 0=left, 1=middle, 2=right
console.log('Client X: ' + event.clientX); // X position in viewport
console.log('Client Y: ' + event.clientY); // Y position in viewport
console.log('Page X: ' + event.pageX); // X position in document
console.log('Page Y: ' + event.pageY); // Y position in document
});
</script>
event.target vs event.currentTarget
Two of the most important properties on the Event object are target and currentTarget. They may seem similar but serve very different purposes, and confusing them is a common source of bugs.
event.target is the element that actually triggered the event -- the deepest element that was clicked, typed in, or interacted with. event.currentTarget is the element that the event listener is attached to. When using event delegation (attaching a listener to a parent element), these two properties will often be different elements.
Example: target vs currentTarget
<ul id="taskList">
<li><span class="task-name">Buy groceries</span> <button class="delete">X</button></li>
<li><span class="task-name">Walk the dog</span> <button class="delete">X</button></li>
<li><span class="task-name">Read a book</span> <button class="delete">X</button></li>
</ul>
<script>
const taskList = document.getElementById('taskList');
// Listener is on the <ul>, but clicks happen on child elements
taskList.addEventListener('click', function(event) {
// currentTarget is always the <ul> (where listener is attached)
console.log('currentTarget: ' + event.currentTarget.tagName); // "UL"
// target is whatever was actually clicked
console.log('target: ' + event.target.tagName);
// Could be "BUTTON", "SPAN", or "LI" depending on what was clicked
// Use target to determine what action to take
if (event.target.classList.contains('delete')) {
const listItem = event.target.closest('li');
listItem.remove();
console.log('Task deleted!');
}
});
</script>
event.target to identify what was clicked and event.currentTarget when you need to reference the element the listener is on. A common pattern is to use event.target.closest(selector) to find the relevant ancestor of the clicked element, which handles cases where the user clicks on a child element inside your target.preventDefault: Stopping Default Browser Behavior
Many events have a default behavior built into the browser. Clicking a link navigates to a new page. Submitting a form reloads the page. Pressing a key in an input types a character. The event.preventDefault() method stops this default behavior while still letting your JavaScript code run.
Example: Using preventDefault
<!-- Prevent link navigation -->
<a href="https://example.com" id="myLink">Go to Example</a>
<!-- Prevent form submission -->
<form id="loginForm">
<input type="text" id="user" placeholder="Username" required>
<input type="password" id="pass" placeholder="Password" required>
<button type="submit">Login</button>
</form>
<script>
// Prevent link from navigating
const link = document.getElementById('myLink');
link.addEventListener('click', function(event) {
event.preventDefault();
console.log('Link click prevented. Would have gone to: ' + link.href);
// You can now handle navigation with JavaScript
});
// Prevent form from reloading the page
const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', function(event) {
event.preventDefault();
const user = document.getElementById('user').value;
const pass = document.getElementById('pass').value;
// Validate and submit via JavaScript instead
if (user.length < 3) {
console.log('Username too short!');
return;
}
console.log('Submitting via fetch...');
// fetch('/api/login', { method: 'POST', body: ... })
});
</script>
stopPropagation: Controlling Event Flow
Events in the DOM propagate -- they travel through the DOM tree in a process called event bubbling. When you click a button inside a div inside the body, the click event fires on the button first, then bubbles up to the div, then to the body, then to the document. The event.stopPropagation() method stops this bubbling, preventing parent elements from receiving the event.
Example: stopPropagation
<div id="outer" style="padding:30px;background:#f0f0f0;">
Outer div
<div id="inner" style="padding:30px;background:#ddd;">
Inner div
<button id="deepBtn">Deep Button</button>
</div>
</div>
<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const deepBtn = document.getElementById('deepBtn');
outer.addEventListener('click', function() {
console.log('Outer clicked');
});
inner.addEventListener('click', function() {
console.log('Inner clicked');
});
// Without stopPropagation: clicking the button logs all three
// With stopPropagation: clicking the button logs only "Button clicked"
deepBtn.addEventListener('click', function(event) {
event.stopPropagation();
console.log('Button clicked');
});
</script>
stopPropagation() can break other event handlers higher up in the DOM tree that depend on event bubbling. For example, a dropdown menu that closes when you click outside of it relies on the click event bubbling up to the document. If you stop propagation on an element, that dropdown may never close. Use stopPropagation sparingly and only when you have a clear reason to prevent bubbling.Removing Event Listeners
To remove an event listener, you use the removeEventListener method. The key requirement is that you must pass the exact same function reference that was used in addEventListener. This means anonymous functions cannot be removed because you have no reference to pass. Always use named functions when you plan to remove listeners later.
Example: Removing Event Listeners
<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>
<div id="output"></div>
<script>
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const output = document.getElementById('output');
// Named function -- can be removed later
function handleMouseMove(event) {
output.textContent = 'Mouse: ' + event.clientX + ', ' + event.clientY;
}
// Add the listener
startBtn.addEventListener('click', function() {
document.addEventListener('mousemove', handleMouseMove);
console.log('Mouse tracking started');
});
// Remove the exact same function reference
stopBtn.addEventListener('click', function() {
document.removeEventListener('mousemove', handleMouseMove);
output.textContent = 'Tracking stopped';
console.log('Mouse tracking stopped');
});
// WARNING: This will NOT work!
// document.addEventListener('click', function() { console.log('A'); });
// document.removeEventListener('click', function() { console.log('A'); });
// These are two DIFFERENT anonymous functions, even though code is identical
</script>
removeEventListener only works with named function references. Always define your handler as a named function or store it in a variable if you need to remove it later.The once Option
Sometimes you want an event handler to fire only one time and then automatically remove itself. Instead of manually calling removeEventListener inside the handler, you can pass the once option. This is cleaner and less error-prone.
Example: Using the once Option
<button id="welcomeBtn">Show Welcome Message</button>
<button id="loadMore">Load More Data</button>
<script>
// This handler fires only ONCE, then removes itself automatically
const welcomeBtn = document.getElementById('welcomeBtn');
welcomeBtn.addEventListener('click', function() {
alert('Welcome! This message appears only once.');
}, { once: true });
// Clicking the button again after the first click does nothing
// Practical use: one-time data loading
const loadMore = document.getElementById('loadMore');
loadMore.addEventListener('click', function() {
console.log('Loading initial data...');
loadMore.textContent = 'Data Loaded';
loadMore.disabled = true;
}, { once: true });
</script>
The passive Option
The passive option tells the browser that your event handler will never call preventDefault(). This is a performance optimization, particularly important for scroll and touchmove events. When the browser knows you will not prevent the default behavior, it can start scrolling immediately without waiting for your JavaScript to execute, resulting in smoother scroll performance.
Example: Using the passive Option
<script>
// Passive scroll listener -- browser can scroll immediately
window.addEventListener('scroll', function() {
// Do something lightweight, like updating a progress bar
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
console.log('Scrolled: ' + Math.round(scrollPercent) + '%');
}, { passive: true });
// Passive touch listener for mobile -- smoother touch scrolling
document.addEventListener('touchmove', function(event) {
// Track touch position without blocking scroll
console.log('Touch at: ' + event.touches[0].clientY);
}, { passive: true });
// You can combine options
document.addEventListener('wheel', function(event) {
console.log('Wheel delta: ' + event.deltaY);
}, { passive: true, capture: false });
// WARNING: If you try to call preventDefault() in a passive listener,
// the browser will ignore it and log a warning in the console
</script>
passive: true for touchstart and touchmove events at the document level. However, explicitly setting { passive: true } on your scroll and touch listeners is a best practice because it documents your intent and ensures consistent behavior across all browsers and all elements.Multiple Listeners on the Same Element
One of the greatest advantages of addEventListener is that you can attach multiple listeners of the same event type to the same element. Each listener runs independently and in the order they were registered. This allows different parts of your code to respond to the same event without conflicting with each other.
Example: Multiple Listeners
<button id="multiBtn">Multi-Handler Button</button>
<script>
const multiBtn = document.getElementById('multiBtn');
// Handler 1: Log the event
multiBtn.addEventListener('click', function(event) {
console.log('Handler 1: Logging click at ' + event.timeStamp);
});
// Handler 2: Update the UI
multiBtn.addEventListener('click', function() {
multiBtn.textContent = 'Clicked!';
});
// Handler 3: Track analytics
multiBtn.addEventListener('click', function() {
console.log('Handler 3: Sending analytics event');
// sendAnalytics('button_click', { button: 'multiBtn' });
});
// Handler 4: Visual feedback
multiBtn.addEventListener('click', function() {
multiBtn.style.transform = 'scale(0.95)';
setTimeout(function() {
multiBtn.style.transform = 'scale(1)';
}, 150);
});
// All four handlers fire in order: 1, 2, 3, 4
</script>
Real-World Example: Button Click with Loading State
A common pattern in web applications is showing a loading state when a button is clicked, performing an asynchronous operation, and then restoring the button. This demonstrates several event concepts working together.
Example: Button Click with Loading State
<button id="saveBtn" class="btn-save">Save Changes</button>
<div id="status"></div>
<script>
const saveBtn = document.getElementById('saveBtn');
const status = document.getElementById('status');
saveBtn.addEventListener('click', function(event) {
// Prevent double-clicks
if (saveBtn.disabled) return;
// Show loading state
const originalText = saveBtn.textContent;
saveBtn.textContent = 'Saving...';
saveBtn.disabled = true;
// Simulate async operation (like a fetch request)
setTimeout(function() {
// Restore button state
saveBtn.textContent = originalText;
saveBtn.disabled = false;
// Show success message
status.textContent = 'Changes saved successfully!';
status.style.color = 'green';
// Clear status after 3 seconds
setTimeout(function() {
status.textContent = '';
}, 3000);
}, 2000);
});
</script>
Real-World Example: Form Validation and Submission
Form handling is one of the most common uses of events. This example shows how to validate a form in real-time using input events and handle submission with the submit event, preventing the default page reload.
Example: Complete Form Handling
<form id="registrationForm">
<div class="form-group">
<label for="regEmail">Email</label>
<input type="email" id="regEmail" required>
<span id="emailError" class="error"></span>
</div>
<div class="form-group">
<label for="regPassword">Password</label>
<input type="password" id="regPassword" required minlength="8">
<span id="passError" class="error"></span>
</div>
<button type="submit" id="regSubmit">Register</button>
</form>
<script>
const regForm = document.getElementById('registrationForm');
const regEmail = document.getElementById('regEmail');
const regPassword = document.getElementById('regPassword');
const emailError = document.getElementById('emailError');
const passError = document.getElementById('passError');
// Real-time email validation using input event
regEmail.addEventListener('input', function(event) {
const value = event.target.value;
if (value && !value.includes('@')) {
emailError.textContent = 'Please include an @ symbol';
} else if (value && !value.includes('.')) {
emailError.textContent = 'Please include a valid domain';
} else {
emailError.textContent = '';
}
});
// Real-time password validation
regPassword.addEventListener('input', function(event) {
const value = event.target.value;
if (value.length > 0 && value.length < 8) {
passError.textContent = 'Password must be at least 8 characters';
} else {
passError.textContent = '';
}
});
// Form submission
regForm.addEventListener('submit', function(event) {
event.preventDefault();
// Final validation
const email = regEmail.value.trim();
const password = regPassword.value;
let isValid = true;
if (!email.includes('@') || !email.includes('.')) {
emailError.textContent = 'Please enter a valid email';
isValid = false;
}
if (password.length < 8) {
passError.textContent = 'Password must be at least 8 characters';
isValid = false;
}
if (isValid) {
console.log('Form is valid! Submitting...');
console.log('Email: ' + email);
// In real app: fetch('/api/register', { ... })
}
});
</script>
Real-World Example: Page Load Initialization
Properly initializing your application when the page loads is critical. Using DOMContentLoaded ensures your JavaScript runs at the right time, after the HTML is ready but before all external resources finish loading.
Example: Page Load Initialization
<script>
// Initialize the app when the DOM is ready
document.addEventListener('DOMContentLoaded', function() {
console.log('App initializing...');
// Set up navigation
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(function(link) {
link.addEventListener('click', function(event) {
event.preventDefault();
navLinks.forEach(function(l) { l.classList.remove('active'); });
link.classList.add('active');
console.log('Navigated to: ' + link.getAttribute('href'));
});
});
// Set up scroll-to-top button
const scrollTopBtn = document.getElementById('scrollTop');
if (scrollTopBtn) {
window.addEventListener('scroll', function() {
if (window.scrollY > 300) {
scrollTopBtn.style.display = 'block';
} else {
scrollTopBtn.style.display = 'none';
}
}, { passive: true });
scrollTopBtn.addEventListener('click', function() {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
// Set up resize handler with debounce
let resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
console.log('Resize settled at: ' + window.innerWidth + 'x' + window.innerHeight);
// Update layout calculations here
}, 250);
});
console.log('App initialized successfully!');
});
</script>
resize event. Events like resize and scroll fire many times per second. Running expensive operations on every single fire will cause performance issues. Debouncing waits until the user stops resizing for a specified duration before running your code. This is an essential performance pattern for production applications.Real-World Example: Dynamic Content with Event Delegation
Event delegation is a pattern where you attach a single event listener to a parent element to handle events from its children, including children that are added dynamically after the listener is set up. This works because of event bubbling -- events fired on child elements bubble up to parent elements.
Example: Todo List with Event Delegation
<div id="todoApp">
<input type="text" id="todoInput" placeholder="Add a new task...">
<button id="addTodo">Add</button>
<ul id="todoList">
<li>
<span class="todo-text">Learn JavaScript events</span>
<button class="complete-btn">Done</button>
<button class="delete-btn">Delete</button>
</li>
</ul>
</div>
<script>
const todoInput = document.getElementById('todoInput');
const addTodo = document.getElementById('addTodo');
const todoList = document.getElementById('todoList');
// Add new todo
addTodo.addEventListener('click', function() {
const text = todoInput.value.trim();
if (!text) return;
const li = document.createElement('li');
li.innerHTML = '<span class="todo-text">' + text + '</span> ' +
'<button class="complete-btn">Done</button> ' +
'<button class="delete-btn">Delete</button>';
todoList.appendChild(li);
todoInput.value = '';
todoInput.focus();
});
// Handle Enter key in input
todoInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
addTodo.click();
}
});
// Event delegation: single listener handles all todo item buttons
// This works for existing AND future items
todoList.addEventListener('click', function(event) {
// Handle complete button
if (event.target.classList.contains('complete-btn')) {
const todoText = event.target.previousElementSibling;
todoText.style.textDecoration = 'line-through';
todoText.style.opacity = '0.5';
event.target.disabled = true;
}
// Handle delete button
if (event.target.classList.contains('delete-btn')) {
const listItem = event.target.closest('li');
listItem.remove();
}
});
</script>
Event Options Summary
Here is a complete reference of all the options you can pass to addEventListener and the key properties of the Event object.
Quick Reference: addEventListener Options and Event Properties
// addEventListener OPTIONS
element.addEventListener('click', handler, {
once: true, // Handler fires once then auto-removes
passive: true, // Promise not to call preventDefault()
capture: true // Listen during capture phase (top-down) instead of bubble phase
});
// KEY EVENT OBJECT PROPERTIES
event.type // Event type string: "click", "submit", "keydown", etc.
event.target // Element that triggered the event
event.currentTarget // Element the listener is attached to
event.timeStamp // When the event occurred (ms since page load)
event.isTrusted // true if user-initiated, false if script-initiated
// KEY EVENT OBJECT METHODS
event.preventDefault() // Stop the default browser behavior
event.stopPropagation() // Stop the event from bubbling up
// MOUSE EVENT PROPERTIES
event.clientX / event.clientY // Position relative to viewport
event.pageX / event.pageY // Position relative to document
event.button // Which mouse button (0=left, 1=middle, 2=right)
// KEYBOARD EVENT PROPERTIES
event.key // The key value: "Enter", "Escape", "a", "A", etc.
event.code // Physical key code: "KeyA", "Enter", "Space", etc.
event.altKey // true if Alt key is held
event.ctrlKey // true if Ctrl key is held
event.shiftKey // true if Shift key is held
event.metaKey // true if Meta (Cmd/Windows) key is held
// REMOVING LISTENERS
element.removeEventListener('click', namedFunction);
// Must pass the EXACT same function reference used in addEventListener
Practice Exercise
Build a complete interactive page that demonstrates your mastery of events. Create the following: (1) A character counter for a textarea that updates in real-time using the input event, showing how many characters remain out of a 280-character limit, with the counter turning red when fewer than 20 characters remain. (2) A contact form with three fields (name, email, message) that validates each field using input events for real-time feedback and the submit event for final validation, using preventDefault to stop the form from reloading the page. (3) A list of items where new items can be added via a text input and Enter key, and each item has a delete button -- use event delegation with a single listener on the parent list. (4) A "back to top" button that appears only after scrolling down 200 pixels, using a scroll event with the passive option. (5) Wrap all initialization code inside a DOMContentLoaded listener. (6) Add a one-time welcome notification using the once option that shows when the user first clicks anywhere on the page. Test all interactions, verify that event.target and event.currentTarget behave as expected in your delegation handler, and confirm that removing an event listener properly stops the associated behavior.