JavaScript Essentials

Events: addEventListener & the Event Object

45 min Lesson 24 of 60

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>
Note: You can attach multiple event listeners of the same type to the same element. All three click handlers in the example above will fire when the button is clicked, in the order they were registered. This is one of the key advantages of 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>
Common Mistake: Using inline HTML event attributes like 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>
Pro Tip: The 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>
Note: The difference between 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>
Pro Tip: When working with event delegation, always use 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>
Common Mistake: Overusing 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>
Note: Even if two anonymous functions have identical code, they are different objects in memory. JavaScript compares functions by reference, not by their source code. That is why 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>
Pro Tip: Modern browsers automatically set 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>
Pro Tip: Notice the debounce pattern on the 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.