Academic Block

Event Listeners Get Certified

Event listeners make web pages interactive. They let your JavaScript code respond when the user clicks a button, types into a field, resizes the window, or performs many other actions.

In this lesson you will learn how to attach and remove listeners, how the event object works, mouse and keyboard events, form events, event bubbling and propagation, preventDefault, event delegation, window/document events, and custom events.

Adding Event Listeners

The modern and preferred way to attach a listener is element.addEventListener(type, handler).

What this code does: Register a click handler. When the (simulated) click occurs, the handler runs and logs a message.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) {
            listeners[type] = listeners[type] || [];
            listeners[type].push(handler);
        },
        simulate(type) {
            (listeners[type] || []).forEach(fn => fn({ type: type }));
        }
    };
}

const button = makeElement("button");
button.addEventListener("click", function () {
    console.log("Button was clicked!");
});
button.simulate("click");

The Event Object

Every handler receives an event object that describes what happened (type, target, key, coordinates, etc.).

What this code does: The handler receives an event object. We log its type and the target element.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) {
            listeners[type] = handler;
        },
        simulate(type, extra) {
            if (listeners[type]) {
                listeners[type](Object.assign({ type: type, target: this }, extra || {}));
            }
        }
    };
}

const btn = makeElement("button");
btn.addEventListener("click", function (event) {
    console.log("Event type:", event.type);
    console.log("Target tag:", event.target.tag);
});
btn.simulate("click");

Removing Event Listeners

To remove a listener you must pass the same function reference that was used when adding it. Anonymous functions cannot be removed later.

What this code does: A named function is added, then removed. After removal the handler no longer runs.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) {
            listeners[type] = listeners[type] || [];
            listeners[type].push(handler);
        },
        removeEventListener(type, handler) {
            if (listeners[type]) {
                listeners[type] = listeners[type].filter(fn => fn !== handler);
            }
        },
        simulate(type) {
            (listeners[type] || []).forEach(fn => fn({ type: type }));
        }
    };
}

const button = makeElement("button");
function onClick() {
    console.log("Clicked");
}
button.addEventListener("click", onClick);
button.simulate("click");          // runs
button.removeEventListener("click", onClick);
button.simulate("click");          // does nothing

Mouse Events

Common mouse events include click, dblclick, mouseenter, mouseleave, and mousemove.

What this code does: Simulate enter, click, and leave on a button. Each handler logs a short message.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) { listeners[type] = handler; },
        simulate(type) { if (listeners[type]) listeners[type]({ type: type }); }
    };
}

const card = makeElement("div");
card.addEventListener("mouseenter", () => console.log("Mouse entered"));
card.addEventListener("click", () => console.log("Card clicked"));
card.addEventListener("mouseleave", () => console.log("Mouse left"));

card.simulate("mouseenter");
card.simulate("click");
card.simulate("mouseleave");

Keyboard Events

keydown and keyup fire when keys are pressed or released. The event object provides event.key and event.code.

What this code does: A keydown handler logs the pressed key. Useful for shortcuts or form controls.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) { listeners[type] = handler; },
        simulate(type, data) { if (listeners[type]) listeners[type](data); }
    };
}

const input = makeElement("input");
input.addEventListener("keydown", function (event) {
    console.log("Key pressed:", event.key);
    if (event.key === "Enter") {
        console.log("Enter was pressed – submit form");
    }
});
input.simulate("keydown", { key: "a" });
input.simulate("keydown", { key: "Enter" });

Form Events

Important form-related events are submit, input, change, focus, and blur.

What this code does: Listen for input changes and form submission. preventDefault stops the real browser navigation/submit.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(type, handler) { listeners[type] = handler; },
        simulate(type, data) { if (listeners[type]) listeners[type](data || {}); }
    };
}

const form = makeElement("form");
const field = makeElement("input");

field.addEventListener("input", function (e) {
    console.log("Current value:", e.value);
});
form.addEventListener("submit", function (e) {
    if (e.preventDefault) e.preventDefault();
    console.log("Form submitted (default prevented)");
});

field.simulate("input", { value: "hello" });
form.simulate("submit", {
    preventDefault() { this.defaultPrevented = true; }
});

Event Bubbling

By default most events bubble: after the target handler runs, the same event travels up through the parent elements.

What this code does: A click on a child also triggers the parent’s listener because of bubbling.

function makeElement(tag, name) {
    const listeners = {};
    return {
        name: name,
        tag: tag,
        parent: null,
        addEventListener(type, handler) { listeners[type] = handler; },
        dispatch(type) {
            if (listeners[type]) listeners[type]({ type: type, target: this });
            if (this.parent) this.parent.dispatch(type);
        }
    };
}

const parent = makeElement("div", "parent");
const child = makeElement("button", "child");
child.parent = parent;

parent.addEventListener("click", () => console.log("Parent received click"));
child.addEventListener("click", () => console.log("Child received click"));

child.dispatch("click");

stopPropagation()

Calling event.stopPropagation() prevents the event from continuing to bubble (or capture).

What this code does: The child stops the event, so the parent handler never runs.

function makeElement(tag, name) {
    const listeners = {};
    return {
        name: name,
        parent: null,
        addEventListener(type, handler) { listeners[type] = handler; },
        dispatch(type) {
            const event = {
                type: type,
                stopped: false,
                stopPropagation() { this.stopped = true; }
            };
            if (listeners[type]) listeners[type](event);
            if (!event.stopped && this.parent) this.parent.dispatch(type);
        }
    };
}

const parent = makeElement("div", "parent");
const child = makeElement("button", "child");
child.parent = parent;

parent.addEventListener("click", () => console.log("Parent clicked"));
child.addEventListener("click", function (e) {
    console.log("Child clicked – stopping propagation");
    e.stopPropagation();
});

child.dispatch("click");

preventDefault()

Many elements have default browser behavior (links navigate, forms submit). event.preventDefault() cancels that behavior.

What this code does: A link click is intercepted and the default navigation is prevented.

const handleLinkClick = function(event) {
    console.log('Link clicked');
    console.log('Default action: navigate to', event.target.href);
    
    // Prevent navigation
    if (event.preventDefault) {
        event.preventDefault();
        console.log('Navigation prevented!');
    }
    
    console.log('Custom behavior executed instead');
};

// Mock link click event
const linkEvent = {
    type: 'click',
    target: {
        href: 'https://example.com',
        tagName: 'A'
    },
    preventDefault: function() {
        this.defaultPrevented = true;
    },
    defaultPrevented: false
};

handleLinkClick(linkEvent);
console.log('Default prevented:', linkEvent.defaultPrevented);

Window and Document Events

You can also listen for events on the window and document objects.

load Event

The load event fires when the entire page has finished loading.

What this code does: A load handler runs when the page (or a resource) has finished loading.

// Load event handler
const handlePageLoad = function() {
    console.log('Page fully loaded');
    console.log('All resources (images, scripts) are ready');
    console.log('DOM is ready for manipulation');
};

// In browser:
// window.addEventListener('load', handlePageLoad);

console.log('The load event fires when:');
console.log('- HTML is parsed');
console.log('- All stylesheets are loaded');
console.log('- All images are loaded');
console.log('- All scripts are executed');

handlePageLoad();

resize Event

The resize event fires when the browser window is resized.

What this code does: A resize handler responds when the window size changes. Useful for responsive layout adjustments.

// Resize event handler
const handleResize = function(event) {
    console.log('Window resized');
    console.log('New width:', event.innerWidth);
    console.log('New height:', event.innerHeight);
};

// In browser:
// window.addEventListener('resize', handleResize);

console.log('Simulating window resize events:');
console.log('');

// Simulate different window sizes
const sizes = [
    { innerWidth: 1920, innerHeight: 1080 },
    { innerWidth: 1366, innerHeight: 768 },
    { innerWidth: 768, innerHeight: 1024 }
];

sizes.forEach(function(size) {
    handleResize(size);
    console.log('');
});

scroll Event

The scroll event fires when the user scrolls the page.

What this code does: A scroll handler runs as the user scrolls the page or a scrollable element.

// Scroll event handler
const handleScroll = function(event) {
    console.log('Page scrolled');
    console.log('Scroll position Y:', event.scrollY);
    
    if (event.scrollY > 100) {
        console.log('User has scrolled down');
    }
};

// In browser:
// window.addEventListener('scroll', handleScroll);

console.log('Simulating scroll events:');
console.log('');

// Simulate scrolling
const scrollPositions = [0, 50, 150, 300];

scrollPositions.forEach(function(pos) {
    const scrollEvent = { scrollY: pos };
    handleScroll(scrollEvent);
    console.log('');
});

Custom Events

You can create and dispatch custom events using the CustomEvent constructor.

What this code does: Custom events let you define your own event types and dispatch them with dispatchEvent.

// Custom event handler
const handleCustomEvent = function(event) {
    console.log('Custom event triggered!');
    console.log('Event name:', event.type);
    console.log('Custom data:', event.detail);
};

// Create custom event
const customEventData = {
    type: 'userLoggedIn',
    detail: {
        username: 'JohnDoe',
        timestamp: Date.now(),
        message: 'User successfully logged in'
    }
};

console.log('Creating custom event:');
console.log('Event type:', customEventData.type);
console.log('');

// In browser:
// const event = new CustomEvent('userLoggedIn', {
//     detail: { username: 'JohnDoe' }
// });
// element.dispatchEvent(event);

handleCustomEvent(customEventData);

Common Event Types

Event Type Description
click Fires when an element is clicked
dblclick Fires when an element is double-clicked
mouseenter Fires when the mouse enters an element
mouseleave Fires when the mouse leaves an element
mousemove Fires when the mouse moves inside an element
keydown Fires when a key is pressed down
keyup Fires when a key is released
submit Fires when a form is submitted
input Fires when an input value changes
change Fires when an input value changes and loses focus
focus Fires when an element receives focus
blur Fires when an element loses focus
load Fires when the page finishes loading
resize Fires when the window is resized
scroll Fires when the page is scrolled

Practical Example: Interactive Counter

What this code does: A simple counter: click handlers increment or decrement a value and log the result.

// Counter application
let count = 0;

const increaseCounter = function() {
    count++;
    console.log('Counter increased to:', count);
    return count;
};

const decreaseCounter = function() {
    count--;
    console.log('Counter decreased to:', count);
    return count;
};

const resetCounter = function() {
    count = 0;
    console.log('Counter reset to:', count);
    return count;
};

// In browser, these would be attached to buttons:
// btnIncrease.addEventListener('click', increaseCounter);
// btnDecrease.addEventListener('click', decreaseCounter);
// btnReset.addEventListener('click', resetCounter);

console.log('Interactive Counter Simulation');
console.log('Initial count:', count);
console.log('');

console.log('Clicking increase button:');
increaseCounter();
increaseCounter();
increaseCounter();
console.log('');

console.log('Clicking decrease button:');
decreaseCounter();
console.log('');

console.log('Clicking reset button:');
resetCounter();

Best Practices for Event Listeners

  • Use addEventListener() instead of inline event handlers like onclick.
  • Use named functions instead of anonymous functions when you need to remove event listeners later.
  • Use event delegation for dynamic content or many child elements.
  • Always call event.preventDefault() when necessary to prevent unwanted default behavior.
  • Use event.stopPropagation() carefully to avoid breaking event bubbling when not needed.
  • Remove event listeners when they are no longer needed to prevent memory leaks.
  • Be mindful of performance when attaching event listeners to events that fire frequently (like scroll or mousemove).
  • Use meaningful function names for event handlers.

Common Mistakes With Event Listeners

  • Trying to remove an anonymous function event listener.
  • Forgetting to call event.preventDefault() when handling form submissions.
  • Attaching too many event listeners instead of using event delegation.
  • Not checking if an element exists before adding an event listener.
  • Using onclick instead of addEventListener(), which can overwrite previous handlers.
  • Not understanding event bubbling and propagation.

Quick Summary

  • Event listeners make web pages interactive by responding to user actions.
  • Use addEventListener() to attach event listeners to elements.
  • The event object contains information about the event.
  • Common mouse events include click, dblclick, mouseenter, mouseleave, and mousemove.
  • Common keyboard events include keydown and keyup.
  • Form events include submit, input, change, focus, and blur.
  • Use removeEventListener() to remove event listeners.
  • Events bubble up from the target element to its ancestors by default.
  • Use event.stopPropagation() to stop event bubbling.
  • Use event.preventDefault() to prevent default browser behavior.
  • Event delegation allows you to attach one listener to a parent instead of many listeners to children.
  • You can create custom events using CustomEvent.

Try It Yourself

Experiment with the code below. Try adding different event types, modifying the handlers, or creating new event scenarios.

What this code does: Practice example: simulate enter, click, and leave on a button. Try changing the handlers or adding another event, then click Test My Code.

// Interactive button simulation
let clickCount = 0;

const handleButtonClick = function() {
    clickCount++;
    console.log('Button clicked', clickCount, 'times');
};

const handleMouseEnter = function() {
    console.log('Mouse entered button area');
};

const handleMouseLeave = function() {
    console.log('Mouse left button area');
};

// In browser:
// button.addEventListener('click', handleButtonClick);
// button.addEventListener('mouseenter', handleMouseEnter);
// button.addEventListener('mouseleave', handleMouseLeave);

console.log('Simulating user interactions:');
console.log('');

handleMouseEnter();
handleButtonClick();
handleButtonClick();
handleMouseLeave();

console.log('');
console.log('Total clicks:', clickCount);
Tip: Event listeners are essential for creating interactive web applications. Practice using different event types and understand how events propagate through the DOM to become proficient in JavaScript.

Understanding event listeners is crucial for building interactive websites and applications. Mastering events will help you create responsive user interfaces and handle user interactions effectively.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close