MGU Computer Science Series — Web Development: HTML, CSS, and JavaScript
Part IV: JavaScript — Interaction and Logic
Events · Listeners · The DOM · Feedback · Debugging
Chapter 21 of 30
21
MGU Computer Science Series  ·  Web Development: HTML, CSS, and JavaScript  ·  Library Textbook Edition

Chapter 21:
Events and User
Interaction

Listening for what users do — clicks, keystrokes, and form actions — and writing clear, organized JavaScript that responds

■ Part IV of VII ■

■ 10 Sections  •  Chapter 21 of 30 ■

Builds on Chapters 19–20 — requires basic JavaScript variables, functions, and DOM selection

Chapter Introduction and Learning Objectives

Every interactive website responds to something a user does. A button is clicked. A key is pressed. A form is submitted. A mouse moves across an element. These moments are called events, and JavaScript's ability to listen for them and respond to them is what transforms a static page into a living interface.

In Chapter 20, you wrote functions. In this chapter, you will connect those functions to user actions. You will learn how to set up event listeners, how to respond to clicks and inputs, how to work with the event object, how to prevent default browser behavior, and how to give users clear feedback. You will also learn how to find and fix the most common event-related bugs. By the end of this chapter, you will be able to build pages that genuinely respond to what users do — not through tricks, but through clear, organized, readable code.

Opening Question

A button on a page does nothing by itself — it is just a labeled box until something tells the browser what to do when it is pressed. A text field collects keystrokes, but nothing happens with them unless code is listening. How does JavaScript know when a user has acted, and how does it decide what to do next? The answer is a three-step pattern that runs underneath nearly every interactive feature on the web: listen, detect, respond. By the end of this chapter, you will understand that pattern well enough to build your own interactive pages from scratch.

10Sections
10Objectives
17Key Terms
30Total Chapters

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Define an event and explain the listen–detect–respond model of JavaScript interactivity.
  2. 2Use addEventListener to attach a handler function to an element's event.
  3. 3Distinguish between anonymous and named event handler functions, and explain why names matter.
  4. 4Explain the difference between click, input, change, focus, and blur events and when to use each.
  5. 5Use the type attribute to control button behavior inside and outside forms.
  6. 6Read and use properties of the event object, including event.target and event.key.
  7. 7Use event.preventDefault() to intercept link navigation and form submission appropriately.
  8. 8Design event handler functions that are short, focused, named, and reusable.
  9. 9Provide accessible feedback to users using text updates, CSS classes, and aria-live.
  10. 10Identify and fix the most common event-related bugs in JavaScript code.

Sections at a Glance

Sections 21.1 – 21.3
The Event Model: Listening, Detecting, Responding
Events · Event Listeners · Click Events

What Events Are

When you use a website, you are constantly doing things. You click buttons. You type into fields. You scroll down a page. You move your mouse over images. You press Enter to submit a search. You tab from one form field to the next.

Each of these actions is an event — a signal that something happened in the browser. JavaScript can listen for these events and run code in response to them. This is the fundamental model of interactive web pages: something happens, JavaScript notices, JavaScript reacts.

Events Are Not Just Mouse Clicks

Beginners sometimes think of events as only button clicks. Events are much broader than that. Here is a sample of the kinds of events browsers can detect:

CategoryExamples
Mouseclick, dblclick, mouseenter, mouseleave, mouseover
Keyboardkeydown, keyup, keypress
Formsubmit, input, change, focus, blur
DocumentDOMContentLoaded, scroll, resize
Touchtouchstart, touchend

You do not need to memorize all of these right now. What matters at this stage is understanding that events are happening constantly as users interact with the page, and JavaScript can respond to them.

The Event Model: Listen, Detect, Respond

JavaScript's interaction model follows a simple three-step pattern:

1. Listen — Tell JavaScript which element to watch, and which event to watch for.

2. Detect — The browser notices when that event happens on that element.

3. Respond — JavaScript runs a function you wrote to handle it.

This pattern is the foundation of all JavaScript interactivity. Everything in this chapter follows this model.

Study Note

Events happen on specific HTML elements. A click event on a button is different from a click event on a paragraph. An input event on a text field is different from an input event on a checkbox. When you set up an event listener, you always attach it to a specific element on the page.

Event Listeners

To respond to an event, you need to set up an event listener. An event listener is an instruction you give to a specific element: "When this event happens on you, run this function."

The addEventListener Method

The standard way to set up an event listener in modern JavaScript is the addEventListener method.

Syntax
element.addEventListener(eventType, handlerFunction);
  • element is the HTML element you want to watch. You get this using document.querySelector() or a similar method.
  • eventType is a string describing the event you want to listen for, such as 'click', 'input', or 'submit'.
  • handlerFunction is the function you want to run when the event happens.

A First Event Listener

Here is the simplest possible event listener. It watches a button for a click, and when the click happens, it runs a function.

HTML
<button id="greet-button">Say Hello</button>
JavaScript
const greetButton = document.querySelector('#greet-button');

greetButton.addEventListener('click', function () {
  console.log('Hello!');
});

When the user clicks the button, the console displays: Hello!

Walk through this line by line:

  • document.querySelector('#greet-button') finds the button element in the page.
  • That element is stored in the variable greetButton.
  • addEventListener('click', ...) tells the button to listen for a click event.
  • The function passed as the second argument runs when the click happens.

Named Functions Versus Anonymous Functions

In the example above, the function has no name — it is defined directly inside addEventListener. This is called an anonymous function. It works fine for simple cases. You can also define the function separately and pass it by name, which is often cleaner and easier to read as your code grows.

function sayHello() {
  console.log('Hello!');
}

const greetButton = document.querySelector('#greet-button');
greetButton.addEventListener('click', sayHello);

Notice: when you pass a function by name to addEventListener, you write sayHello, not sayHello(). The parentheses would call the function immediately. Without the parentheses, you are passing the function itself to be called later.

Common Mistake

Writing addEventListener('click', sayHello()) calls sayHello immediately when the page loads, not when the button is clicked. Leave off the parentheses when passing functions to addEventListener.

Multiple Event Listeners on One Element

You can attach more than one event listener to the same element. Each listener is independent.

const button = document.querySelector('#my-button');

button.addEventListener('click', function () {
  console.log('Listener one ran.');
});

button.addEventListener('click', function () {
  console.log('Listener two ran.');
});

Both functions will run when the button is clicked, in the order they were added.

Removing Event Listeners

You can also remove an event listener using removeEventListener. This requires using a named function — anonymous functions cannot be removed because there is no way to reference them again later. Removing event listeners is an advanced topic, but it is good to know that it is possible.

Click Events

The click event is the most common event in web development. Every button, link, card, toggle, and interactive element on the web relies on click events in some form.

Listening for a Click

HTML
<button id="launch-button">Launch</button>
<p id="status">Status: waiting.</p>
JavaScript
const launchButton = document.querySelector('#launch-button');
const statusText = document.querySelector('#status');

launchButton.addEventListener('click', function () {
  statusText.textContent = 'Status: launched!';
});

When the user clicks the button, the paragraph text changes from "Status: waiting." to "Status: launched!" This example combines two skills: event listening from this chapter, and DOM updates from Chapter 22. (You are previewing Chapter 22's content here — updating the page is covered in full detail there.)

Any Element Can Receive a Click Listener

Although buttons are the most natural targets for clicks, addEventListener can be attached to almost any HTML element: a paragraph, a heading, a list item, a card, an image.

const card = document.querySelector('.product-card');

card.addEventListener('click', function () {
  console.log('Card was clicked.');
});
Accessibility Caution

Interactive actions should normally happen on elements that users expect to be interactive — buttons, links, and labeled controls. Putting click listeners on arbitrary div elements can create accessibility problems because screen readers and keyboard users will not know those elements are interactive.

Double-Click

The dblclick event fires when a user clicks an element twice in rapid succession.

document.querySelector('#edit-field').addEventListener('dblclick', function () {
  console.log('Double-clicked — entering edit mode.');
});

Use double-click sparingly. Many users, especially on touch devices, do not double-click intuitively.

Sections 21.4 – 21.6
Working with Controls: Buttons, Input, and Handlers
Buttons · Input Events · Handler Function Design

Buttons and Interaction

Buttons are the primary interactive control on web pages. Understanding how buttons work — and how to attach behavior to them — is a core skill.

Use Button for Interactive Actions

A <button> element is meant for triggering actions. It is distinct from a link (<a>), which is meant for navigation.

ElementUse for
<button>Actions (submit, toggle, run something)
<a>Navigation (go somewhere)

Using a button for an action and a link for navigation is correct semantically and also benefits screen reader users, who can distinguish between the two.

Button Types

HTML buttons have a type attribute with three possible values:

  • type="button" — A general-purpose button. Does not submit forms. This is usually what you want when attaching a click listener.
  • type="submit" — Submits the nearest form. This is the default if you do not specify a type inside a form.
  • type="reset" — Clears the form.
Important

Always specify type="button" on buttons that are not meant to submit a form. If you place a button inside a <form> without specifying its type, the browser may treat it as a submit button and reload the page when clicked.

<button type="button" id="toggle-button">Show Details</button>

Toggling Text on a Button

A common pattern is to toggle a button between two states. Here is a simple version:

HTML
<button type="button" id="toggle-button">Show Details</button>
<div id="details" style="display: none;">
  <p>Here are the hidden details.</p>
</div>
JavaScript
const toggleButton = document.querySelector('#toggle-button');
const details = document.querySelector('#details');
let detailsVisible = false;

toggleButton.addEventListener('click', function () {
  if (detailsVisible) {
    details.style.display = 'none';
    toggleButton.textContent = 'Show Details';
    detailsVisible = false;
  } else {
    details.style.display = 'block';
    toggleButton.textContent = 'Hide Details';
    detailsVisible = true;
  }
});

This example introduces the if/else pattern you studied in Chapter 23 (which follows this chapter). For now, read it as: "if the details are visible, hide them; otherwise, show them."

Note on Inline Styles

Setting style.display directly works but mixes JavaScript with visual control. A cleaner pattern — toggling CSS classes instead — is covered in Chapter 22.

Disabling a Button

You can prevent a button from being clicked by setting its disabled property to true.

const submitButton = document.querySelector('#submit-button');
submitButton.disabled = true;

A disabled button is grayed out in most browsers and cannot be clicked. Enabling it again:

submitButton.disabled = false;

Disabling a button after a user submits a form is a useful technique to prevent duplicate submissions.

Input Events

While click events respond to button presses and other single-action interactions, input events respond to users typing, selecting, or modifying form controls.

The input Event

The input event fires every time the value of a form field changes. For a text input, this means it fires on every keystroke.

HTML
<input type="text" id="name-field" placeholder="Enter your name">
<p id="greeting-output">Hello, stranger.</p>
JavaScript
const nameField = document.querySelector('#name-field');
const greetingOutput = document.querySelector('#greeting-output');

nameField.addEventListener('input', function () {
  const currentValue = nameField.value;
  if (currentValue.trim() === '') {
    greetingOutput.textContent = 'Hello, stranger.';
  } else {
    greetingOutput.textContent = 'Hello, ' + currentValue + '!';
  }
});

As the user types into the text field, the paragraph updates in real time.

  • nameField.value retrieves the current text in the input.
  • .trim() removes leading and trailing spaces, so an input of only spaces is treated as empty.

Reading an Input's Current Value

The .value property works for most form fields:

const emailField = document.querySelector('#email');
const currentEmail = emailField.value;
console.log(currentEmail);

For checkboxes, use .checked instead of .value:

const agreeCheckbox = document.querySelector('#agree');
const isChecked = agreeCheckbox.checked; // true or false

For a group of radio buttons, you need to find the one that is currently selected. That is covered in Chapter 24 on forms.

The change Event

The change event is similar to input, but it fires only when the user finishes making a change and moves away (blurs) from the field. For text inputs, change fires when the user clicks elsewhere or presses Tab. For checkboxes and select menus, change fires immediately when the state changes.

const colorSelect = document.querySelector('#color-select');

colorSelect.addEventListener('change', function () {
  console.log('Selected color: ' + colorSelect.value);
});
Choosing Between input and change

Use input when you want to react on every keystroke (live feedback). Use change when you want to react only after the user finishes.

The focus and blur Events

focus fires when a form element receives focus — when the user clicks into it or tabs to it. blur fires when a form element loses focus — when the user clicks elsewhere or tabs away. These events are useful for showing a helper message when a field is active, validating a field when the user leaves it, or highlighting an active field visually.

const emailField = document.querySelector('#email');

emailField.addEventListener('focus', function () {
  console.log('Email field is active.');
});

emailField.addEventListener('blur', function () {
  console.log('Email field lost focus.');
});

Event Handler Functions

An event handler function is the function that runs in response to an event. You have been writing them throughout this chapter. Now it is time to think more carefully about how to write them well.

Keep Handler Functions Focused

A good event handler function does one clear thing. If a click should validate a form, update the page, and log a message, write separate functions for each task and call them from the handler.

function validateForm() {
  // validation logic
}

function updatePage() {
  // page update logic
}

submitButton.addEventListener('click', function () {
  validateForm();
  updatePage();
});

This is more readable and easier to debug than putting all the logic directly inside the anonymous function.

Named Handler Functions Improve Readability

Rather than writing anonymous functions everywhere, you can name your handler functions clearly.

function handleSubmitClick() {
  validateForm();
  updatePage();
}

submitButton.addEventListener('click', handleSubmitClick);

Now when you read the event listener line, it is immediately clear what happens when the button is clicked.

Handler Functions Can Be Reused

Because event handlers are regular functions, you can attach the same handler to multiple elements.

HTML
<button type="button" class="close-button">Close</button>
<button type="button" class="close-button">Close</button>
<button type="button" class="close-button">Close</button>
JavaScript
function handleClose() {
  console.log('A close button was clicked.');
}

const closeButtons = document.querySelectorAll('.close-button');

closeButtons.forEach(function (button) {
  button.addEventListener('click', handleClose);
});

querySelectorAll returns all matching elements. forEach loops through them and attaches the same listener to each one. You will study querySelectorAll and forEach in more detail in Chapter 22 and Chapter 23.

Handler functions shouldHandler functions should not
Be short and focusedContain hundreds of lines of mixed logic
Call other functions for complex workDirectly manipulate everything on the page from one function
Use descriptive namesBe named function1, handler, doStuff
Be easy to read without commentsRequire extensive comments to understand
Sections 21.7 – 21.10
Going Deeper: The Event Object, Defaults, Feedback, and Debugging
Event Object · preventDefault · Accessible Feedback · Debugging

The Event Object at an Introductory Level

When JavaScript calls your event handler function, it automatically passes an argument to it. This argument is the event object — a bundle of information about the event that just happened. You do not have to use the event object, but when you need it, it is there.

Receiving the Event Object

To access the event object, add a parameter to your handler function. The parameter is conventionally named event or e.

document.querySelector('#my-button').addEventListener('click', function (event) {
  console.log(event);
});

When you click the button and look in the console, you will see a large object printed. It contains many properties describing the event.

Useful Properties at an Introductory Level

PropertyWhat it gives you
event.typeThe type of event as a string: 'click', 'input', etc.
event.targetThe specific element that triggered the event
event.keyThe key pressed, for keyboard events
event.preventDefault()A method to stop default browser behavior (covered in 21.8)

Using event.target

event.target is especially useful when one function handles events from multiple elements and you need to know which specific element was clicked or changed.

HTML
<button type="button" class="color-button" data-color="red">Red</button>
<button type="button" class="color-button" data-color="blue">Blue</button>
<button type="button" class="color-button" data-color="green">Green</button>
JavaScript
const colorButtons = document.querySelectorAll('.color-button');

colorButtons.forEach(function (button) {
  button.addEventListener('click', function (event) {
    const chosenColor = event.target.dataset.color;
    console.log('Chosen color: ' + chosenColor);
  });
});

event.target is the specific button that was clicked. dataset.color reads the data-color attribute from that button. This pattern — storing information in data- attributes and reading it from event.target — is a very common and clean way to build interactive elements.

Using event.key for Keyboard Events

When listening for keyboard events, event.key tells you which key was pressed.

document.querySelector('#search-field').addEventListener('keydown', function (event) {
  if (event.key === 'Enter') {
    console.log('Search submitted by pressing Enter.');
  }
});

event.key returns the actual key name as a string: 'Enter', 'Escape', 'ArrowUp', 'a', '1', and so on.

Preventing Default Behavior at an Introductory Level

Some HTML elements have built-in browser behaviors that happen automatically when events occur. Links navigate to a new page when clicked. Form submit buttons reload the page or send form data. Sometimes you need to stop these default behaviors so JavaScript can handle them instead.

event.preventDefault()

Calling event.preventDefault() on the event object stops the browser's default response to that event. The event still fires — your handler function still runs — but the browser's built-in behavior does not happen.

Preventing a Link from Navigating

This is occasionally useful when building interactive navigation menus or single-page apps. In most cases, links should navigate as expected — only prevent navigation when you have a specific reason.

HTML
<a href="https://example.com" id="preview-link">Preview</a>
JavaScript
document.querySelector('#preview-link').addEventListener('click', function (event) {
  event.preventDefault();
  console.log('Link click intercepted — no navigation happened.');
});

The link looks and behaves like a link (including keyboard accessibility), but clicking it does not navigate away from the page.

Preventing a Form from Submitting

This is one of the most important uses of event.preventDefault(). When a form is submitted, the browser's default behavior is to send the form data to a server and reload the page. If you want to handle the form with JavaScript — validate it, display a confirmation, send data with fetch() — you need to prevent that default submission.

HTML
<form id="contact-form">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name">
  <button type="submit">Submit</button>
</form>
JavaScript
const contactForm = document.querySelector('#contact-form');

contactForm.addEventListener('submit', function (event) {
  event.preventDefault();
  const nameValue = document.querySelector('#name').value;
  console.log('Form submitted with name: ' + nameValue);
});

Without event.preventDefault(), the page would reload when the form is submitted. With it, JavaScript takes over and you can do whatever you want — validate the data, display a message, or send the data with fetch().

Use Responsibly

event.preventDefault() is a powerful tool, but use it only when you have a good reason. Preventing link navigation without providing an alternative makes pages unusable. Preventing form submission without replacing it with your own handling leaves users with no feedback. Always think about what the user expects to happen and ensure they still get a useful result.

Giving Feedback to Users

Event listeners are not just about running code internally. They are also about communicating with users. When a user clicks a button, types into a field, or submits a form, they want to know something happened. Giving clear, timely, accessible feedback is a core part of building good interactive pages.

Updating Text on the Page

The most direct form of feedback is changing text the user can see.

HTML
<button type="button" id="save-button">Save</button>
<p id="save-status"></p>
JavaScript
const saveButton = document.querySelector('#save-button');
const saveStatus = document.querySelector('#save-status');

saveButton.addEventListener('click', function () {
  saveStatus.textContent = 'Your work has been saved.';
});

The paragraph starts empty and fills with confirmation text when the user clicks Save.

Showing and Hiding Messages

A common feedback pattern shows a message that was previously hidden.

HTML
<button type="button" id="confirm-button">Confirm</button>
<p id="confirm-message" class="hidden">Your selection has been confirmed.</p>
CSS
.hidden {
  display: none;
}
JavaScript
const confirmButton = document.querySelector('#confirm-button');
const confirmMessage = document.querySelector('#confirm-message');

confirmButton.addEventListener('click', function () {
  confirmMessage.classList.remove('hidden');
});

Rather than setting style.display directly, this approach uses a CSS class (hidden) to control visibility, and JavaScript simply adds or removes that class. This keeps visual control in CSS and behavioral control in JavaScript, which is the clean separation you learned in Chapter 11.

Changing the Button's Own Appearance or Text

Buttons can also confirm their own state by changing their text or appearance.

saveButton.addEventListener('click', function () {
  saveButton.textContent = 'Saved!';
  saveButton.disabled = true;
});

This makes the button confirm the save and prevents the user from clicking it again immediately.

Accessible Feedback

Visual feedback is important, but so is feedback that works for all users. When you show or hide messages with JavaScript, screen readers may not automatically announce the change. Adding aria-live="polite" to a feedback element tells screen readers to announce its content when it changes, without interrupting the user.

HTML
<p id="status-message" aria-live="polite"></p>
JavaScript
document.querySelector('#status-message').textContent = 'Form submitted successfully.';

When the text changes, a screen reader will announce it to the user. Using aria-live="assertive" is for urgent messages that should interrupt the user immediately, such as error alerts. Use polite for most feedback.

Temporary Messages

Sometimes feedback should disappear after a few seconds. JavaScript's setTimeout function runs code after a delay.

saveButton.addEventListener('click', function () {
  saveStatus.textContent = 'Saved!';
  setTimeout(function () {
    saveStatus.textContent = '';
  }, 3000);
});

The message appears immediately, then clears after 3000 milliseconds (3 seconds). setTimeout is not an event listener — it is a browser timer — but it combines naturally with event-driven code.

Accessibility Note

Auto-disappearing messages can be missed by users who read slowly or use screen readers. Use them for supplementary feedback only, not for critical information.

Debugging Event Problems

Event listeners fail in predictable ways. Knowing the most common problems helps you find and fix them quickly.

Problem 1: The Element Was Not Found

If document.querySelector() does not find the element, it returns null. Calling addEventListener on null throws an error.

Cannot read properties of null (reading 'addEventListener')

Causes: the selector is wrong (a typo, wrong class name, wrong ID); the element does not exist in the HTML; or the JavaScript ran before the element was added to the page.

Fix: double-check the selector. Open the Elements panel in developer tools and confirm the element's ID or class name exactly. If the script runs before the HTML is ready, move the <script> tag to the bottom of the <body>, or use the DOMContentLoaded event (see Problem 5).

Problem 2: The Handler Runs Immediately Instead of on Click

This happens when you accidentally call the function instead of passing it.

Wrong
button.addEventListener('click', handleClick());
// handleClick() runs immediately
Correct
button.addEventListener('click', handleClick);
// handleClick is passed as a reference, called later

Problem 3: The Page Reloads Unexpectedly on Form Submit

A button inside a <form> element without type="button" defaults to type="submit". When clicked, it submits the form, which reloads the page.

Fix: add type="button" to any button inside a form that is not meant to submit it. Or use event.preventDefault() on the form's submit listener.

Problem 4: The Event Listener Fires on the Wrong Element

If multiple elements share a class, querySelector returns only the first matching element. If you want all of them, use querySelectorAll and a forEach loop.

Wrong (only attaches to the first .card)
document.querySelector('.card').addEventListener('click', handleClick);
Correct (attaches to all .card elements)
document.querySelectorAll('.card').forEach(function (card) {
  card.addEventListener('click', handleClick);
});

Problem 5: JavaScript Ran Before the DOM Was Ready

If your script runs before the browser has finished reading the HTML, querySelector will not find elements because they do not exist yet.

Fix A (preferred) — script at the bottom of body
  <!-- All your HTML here -->
  <script src="js/main.js"></script>
</body>
Fix B — wait for DOMContentLoaded
document.addEventListener('DOMContentLoaded', function () {
  // All your event listener setup goes here
  const button = document.querySelector('#my-button');
  button.addEventListener('click', handleClick);
});

DOMContentLoaded fires when the browser has finished reading and processing the HTML. Any code inside this listener can safely reference any element on the page.

Problem 6: event.preventDefault() Is in the Wrong Place

event.preventDefault() must be called before other code that might have side effects. If you call it after a long chain of operations, the browser's default may already have triggered.

form.addEventListener('submit', function (event) {
  event.preventDefault(); // First line
  // Validation and other logic follows
});
Using the Console to Debug

When an event listener is not working, add console.log statements inside the handler function to confirm it is running. If the message appears in the console, the listener is working and the problem is elsewhere. If it does not appear, the listener never fired — meaning either the element was not found, or the event is not happening as expected.

Interactive Button Panel

This worked example brings together the major skills from this chapter: attaching event listeners, responding to clicks, reading input, using the event object, giving feedback, and writing organized handler functions.

Goal
Build a small interactive panel
  • ColorsThree color-choosing buttons that display the chosen color as feedback text.
  • GreetingA text input that shows a live greeting as the user types.
  • ResetA reset button that clears everything back to its starting state.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Interactive Panel</title>
  <link rel="stylesheet" href="css/main.css">
</head>
<body>

  <main>
    <h1>Interactive Panel</h1>

    <section aria-labelledby="color-heading">
      <h2 id="color-heading">Choose a Color</h2>
      <div class="button-group">
        <button type="button" class="color-btn" data-color="Red">Red</button>
        <button type="button" class="color-btn" data-color="Blue">Blue</button>
        <button type="button" class="color-btn" data-color="Green">Green</button>
      </div>
      <p id="color-display" aria-live="polite">No color chosen yet.</p>
    </section>

    <section aria-labelledby="greeting-heading">
      <h2 id="greeting-heading">Your Greeting</h2>
      <label for="name-input">Enter your name:</label>
      <input type="text" id="name-input" placeholder="Your name">
      <p id="greeting-display" aria-live="polite">Hello, visitor.</p>
    </section>

    <section>
      <button type="button" id="reset-btn">Reset Everything</button>
    </section>
  </main>

  <script src="js/main.js"></script>
</body>
</html>
JavaScript
// --- Element References ---

const colorButtons = document.querySelectorAll('.color-btn');
const colorDisplay = document.querySelector('#color-display');

const nameInput = document.querySelector('#name-input');
const greetingDisplay = document.querySelector('#greeting-display');

const resetButton = document.querySelector('#reset-btn');


// --- Color Button Handlers ---

function handleColorClick(event) {
  const chosenColor = event.target.dataset.color;
  colorDisplay.textContent = 'You chose: ' + chosenColor;
}

colorButtons.forEach(function (button) {
  button.addEventListener('click', handleColorClick);
});


// --- Name Input Handler ---

function handleNameInput() {
  const name = nameInput.value.trim();
  if (name === '') {
    greetingDisplay.textContent = 'Hello, visitor.';
  } else {
    greetingDisplay.textContent = 'Hello, ' + name + '!';
  }
}

nameInput.addEventListener('input', handleNameInput);


// --- Reset Handler ---

function handleReset() {
  colorDisplay.textContent = 'No color chosen yet.';
  nameInput.value = '';
  greetingDisplay.textContent = 'Hello, visitor.';
}

resetButton.addEventListener('click', handleReset);

Reading the Code

  • Element references — All elements are selected once at the top. This is cleaner than selecting elements inside every handler function.
  • Color buttons — querySelectorAll gets all three buttons. forEach attaches the same handler to each. The handler reads event.target.dataset.color to know which button was clicked, without needing three separate functions.
  • Name input — The input event fires on every keystroke. The handler trims whitespace, checks if the input is empty, and updates the greeting accordingly.
  • Reset — The reset handler sets all elements back to their initial state.
What This Demonstrates

Event listeners on multiple elements with forEach; using event.target to identify which element fired the event; reading data- attributes through dataset; reading input values with .value; giving accessible feedback with aria-live; and keeping handlers named and focused.

Chapter Summary

Key Terms

event
A signal from the browser that something happened, such as a click, keystroke, or form submission.
event listener
An instruction that tells an element to watch for a specific event and run a function when it fires.
addEventListener
The JavaScript method used to attach an event listener to an element.
event type
A string identifying the kind of event to listen for, such as 'click', 'input', or 'submit'.
handler function
The function that runs in response to an event.
anonymous function
A function defined without a name, often written directly inside addEventListener.
event object
An object automatically passed to handler functions containing information about the event, including event.type, event.target, and event.key.
event.target
The specific element on which the event occurred.
event.preventDefault()
A method that stops the browser's default response to an event.
input event
An event that fires every time the value of a form field changes.
change event
An event that fires when a form field's value is committed (when the user leaves the field or makes a selection).
focus event
Fires when an element receives focus.
blur event
Fires when an element loses focus.
data- attribute
A custom HTML attribute used to store information on an element, accessible in JavaScript through the dataset property.
aria-live
An HTML attribute that causes screen readers to announce changes to an element's text content.
DOMContentLoaded
An event that fires when the browser has finished parsing the HTML document and built the DOM.
disabled
An HTML attribute and JavaScript property that prevents a form control from being interacted with.
PracticeCode Reading

Read each code sample carefully and answer the questions that follow.

Code Reading 21-A
<button type="button" id="show-btn">Show Message</button>
<p id="message" style="display: none;">Welcome to the site!</p>
const showBtn = document.querySelector('#show-btn');
const message = document.querySelector('#message');

showBtn.addEventListener('click', function () {
  message.style.display = 'block';
});
  1. What does the user see when the page first loads?
  2. What happens when the user clicks the button?
  3. What would happen if the button had type="submit" and was inside a <form>?
  4. Could the user hide the message again by clicking the button a second time? Why or why not?
Code Reading 21-B
const inputs = document.querySelectorAll('.score-input');

inputs.forEach(function (input) {
  input.addEventListener('input', function () {
    console.log('Score changed: ' + input.value);
  });
});
  1. How many elements does this code attach listeners to?
  2. When does the event fire — on every keystroke or only when the user presses Enter?
  3. What would input.value contain for a text input reading 87?
  4. What would happen if querySelector were used instead of querySelectorAll?
Code Reading 21-C
<form id="search-form">
  <label for="query">Search:</label>
  <input type="text" id="query">
  <button type="submit">Go</button>
</form>
const searchForm = document.querySelector('#search-form');

searchForm.addEventListener('submit', function (event) {
  event.preventDefault();
  const query = document.querySelector('#query').value.trim();
  if (query === '') {
    console.log('Please enter a search term.');
  } else {
    console.log('Searching for: ' + query);
  }
});
  1. What default behavior does event.preventDefault() stop here?
  2. What happens if the user clicks Go with an empty field?
  3. What happens if the user types "ocean tides" and clicks Go?
  4. How would the behavior change if the event listener were attached to the button instead of the form?
Code Reading 21-D
document.querySelector('#color-picker').addEventListener('change', function (event) {
  const chosen = event.target.value;
  document.querySelector('#preview-box').style.backgroundColor = chosen;
});
  1. What kind of element is #color-picker likely to be?
  2. When does the change event fire — on every interaction or after the user makes a selection?
  3. What does event.target.value hold in this context?
  4. What visual result does a user see?
PracticeCode Tracing

Trace through each example carefully, predicting what will be logged to the console or displayed on the page.

Trace 21-1
<button type="button" id="btn-a">A</button>
<button type="button" id="btn-b">B</button>
let clickCount = 0;

document.querySelector('#btn-a').addEventListener('click', function () {
  clickCount = clickCount + 1;
  console.log('Count: ' + clickCount);
});

document.querySelector('#btn-b').addEventListener('click', function () {
  clickCount = 0;
  console.log('Reset. Count: ' + clickCount);
});
  1. The user does: click A, click A, click A, click B, click A. What is logged to the console after each click?
Trace 21-2
const field = document.querySelector('#number-field');
const output = document.querySelector('#result');

field.addEventListener('input', function () {
  const val = Number(field.value);
  if (val > 100) {
    output.textContent = 'Too high.';
  } else if (val < 0) {
    output.textContent = 'Too low.';
  } else {
    output.textContent = 'Valid: ' + val;
  }
});
  1. The user types these values in sequence (one keystroke at a time): 1, 10, 105, then clears the field and types -5. What does output.textContent show after each step?
PracticePredict the Result
  1. Predict 21-1. A page has a button with id="toggle". A JavaScript file contains:
    const btn = document.querySelector('#toggle');
    btn.addEventListener('click', function () {
      btn.textContent = 'Clicked!';
    });
    btn.addEventListener('click', function () {
      btn.style.backgroundColor = 'yellow';
    });
    What happens the first time the user clicks the button? What happens the second time?
  2. Predict 21-2. A form contains one text input and a submit button. The JavaScript file contains:
    document.querySelector('form').addEventListener('submit', function (event) {
      console.log('Handler ran.');
    });
    When the user submits the form, what happens? What would be different if event.preventDefault() were added?
  3. Predict 21-3. A developer writes:
    const btn = document.querySelector('#save-btn');
    btn.addEventListener('click', handleSave());
    The page loads. Before the user clicks anything, what happens? What is the bug?
PracticeDebugging and Error Analysis
Debug 21-1
<button type="button" id="launchBtn">Launch</button>
const button = document.querySelector('#launch-btn');
button.addEventListener('click', function () {
  console.log('Launched!');
});
  1. The console shows: Cannot read properties of null (reading 'addEventListener'). What is wrong? How do you fix it?
Debug 21-2
<form id="contact">
  <input type="text" id="email">
  <button>Send</button>
</form>
document.querySelector('button').addEventListener('click', function () {
  console.log('Button clicked.');
});
  1. When the button is clicked, the page reloads and "Button clicked." never appears in the console. What is happening? What is the fix?
Debug 21-3
const cards = document.querySelectorAll('.card');
cards.addEventListener('click', function () {
  console.log('Card clicked.');
});
  1. The console shows: cards.addEventListener is not a function. What is the problem? How do you fix it?
Debug 21-4
saveButton.addEventListener('click', function () {
  statusMessage.textContent = 'Saved!';
  setTimeout(function () {
    statusMessage.textContent = '';
  }, 2000);
});
  1. On the first click, this works. But if the user clicks very quickly multiple times, multiple timers stack up and the message does not clear reliably. What is happening, and what approach could help?
PracticeShort Build Tasks
  1. Build 21-1. Create an HTML page with a button labeled "Change Background." When clicked, change document.body.style.backgroundColor to 'lightblue'. When clicked again, change it back to '' (empty string restores the default). Use a variable to track the current state.
  2. Build 21-2. Create a text input and a paragraph below it. As the user types, display the character count in the paragraph: "Characters: 14" (or however many characters are in the field). Use .value.length to get the character count.
  3. Build 21-3. Create three buttons, each with a data-message attribute containing a different short message. When any button is clicked, display that button's message in a paragraph below the buttons. Use event.target.dataset.message to read the message from the clicked button.
  4. Build 21-4. Create a form with one required text field labeled "Username" and a submit button. Prevent the form from submitting. When the user submits, read the username value. If it is empty (after trimming), display "Username is required" in a red paragraph. If it is not empty, display "Welcome, [username]!" in a green paragraph. Use aria-live="polite" on the message paragraph.
  5. Build 21-5. Create a text input. When the input receives focus, change its border color to blue using JavaScript. When it loses focus, change the border back to its default. Use focus and blur events. Then refactor: instead of setting style.borderColor directly, add and remove a CSS class called input--active.

Accessibility Check

Review the following code and identify any accessibility issues with the event handling and feedback.

<div id="toggle-area" onclick="toggleContent()">
  Show more
</div>
<div id="extra-content" style="display: none;">
  <p>Here is the hidden content.</p>
</div>
function toggleContent() {
  const content = document.querySelector('#extra-content');
  if (content.style.display === 'none') {
    content.style.display = 'block';
  } else {
    content.style.display = 'none';
  }
}
  1. Why is using a <div> for this interaction a problem for keyboard users?
  2. Why is using an onclick attribute in HTML considered less ideal than addEventListener in JavaScript?
  3. How would you rewrite the HTML element to make this interaction keyboard-accessible?
  4. When the content is shown or hidden, how would a screen reader user know the state changed? What could you add to improve this?
  5. Rewrite the HTML and JavaScript to address these issues. Use a <button>, addEventListener, and aria-live.
PracticeMixed Review

These questions connect Chapter 21 material to earlier chapters.

  1. MR 21-1 (Chapters 5, 21): A developer adds a <script src="js/main.js"> tag inside the <head> element of their page. The JavaScript tries to select a button and attach a click listener. The listener never fires. What is the likely cause, and what is the standard fix?
  2. MR 21-2 (Chapters 9, 21): A form has a select menu (<select>) with several options. Write the JavaScript to listen for the change event on the select menu and display the currently selected value in a paragraph.
  3. MR 21-3 (Chapters 11, 21): Explain the difference between changing an element's style in CSS and changing it through JavaScript. When is each approach more appropriate?
  4. MR 21-4 (Chapters 12, 21): A page has both .button and #special-button selectors. The JavaScript attaches click listeners to both. Which element does document.querySelector('#special-button') select? If the element also has the class button, will both event listeners fire when it is clicked?
  5. MR 21-5 (Chapters 20, 21): A handler function reads a value from an input, multiplies it by 1.08 (to add 8% tax), and displays the result. Write this handler function. What type conversion may be necessary? (Hint: input values are always strings.)

Mini-Project: Interactive Button Panel

Project Goal
Build a self-contained interactive panel that combines click events, input events, feedback, and clean code organization.
  • GreetingsA section with three buttons labeled "Morning," "Afternoon," and "Evening." When a button is clicked, display a greeting based on the time of day: "Good morning!", "Good afternoon!", or "Good evening!" Use data-greeting attributes on the buttons. Display the greeting in a paragraph with aria-live="polite".
  • Name fieldA section with a text input labeled "Your Name." As the user types, update a greeting that says "Welcome, [name]!" If the field is empty, display "Welcome, visitor!" instead.
  • CounterA counter section with three buttons: Decrease, Reset, and Increase. Display the current count (starting at 0). Decrease reduces the count by 1 but does not go below 0. Increase adds 1. Reset returns the count to 0.
  • Reset allA Reset All button at the bottom that resets every section to its starting state.

Code requirements: use a separate HTML file, a separate CSS file, and a separate JavaScript file; use type="button" on all buttons; use addEventListener for all interactions; use named handler functions where appropriate; use aria-live="polite" on feedback paragraphs; use data- attributes where appropriate.

Extension (Optional)

Add a keyboard listener so the counter can also be controlled by pressing the Up Arrow and Down Arrow keys. When the counter reaches 10, display a message: "You reached 10!" Clear the message if the count drops below 10.

Chapter 21 Checklist
Use this to confirm your understanding before moving to Chapter 22
Concepts
  • I can explain the listen-detect-respond model of JavaScript events.
  • I can use addEventListener to attach a function to an element's event.
  • I understand why I should pass a function by name, not call it, inside addEventListener.
  • I can explain the difference between click, input, change, focus, and blur events.
  • I can explain when a button should use type="button" instead of the default submit behavior.
  • I can use event.target and event.key to read information from the event object.
  • I can use event.preventDefault() to stop a link from navigating or a form from submitting.
Applications
  • I can write a short, focused, named handler function.
  • I can attach the same handler to multiple elements using querySelectorAll and forEach.
  • I can give users clear feedback by updating text, toggling classes, and using aria-live.
  • I can build a small interactive panel that combines clicks, input, and feedback.
Debugging
  • I can diagnose a "Cannot read properties of null" error caused by a missing or mistyped selector.
  • I know to place scripts at the end of the body, or use DOMContentLoaded, so elements exist before I select them.
  • I can recognize when querySelector should have been querySelectorAll.
Preview — Chapter 22
The DOM: Reading and Changing the Page

Chapter 22 introduces the Document Object Model — the browser's internal representation of your HTML page as a tree of connected nodes. You have been using small pieces of the DOM throughout Chapters 19, 20, and 21 without formally studying it. Chapter 22 explains what the DOM actually is, how to select elements reliably, how to read and change text and attributes, how to add and remove classes, and how to create new elements and add them to the page. The DOM is the bridge between JavaScript and HTML. Understanding it well will make every interaction you write cleaner, more reliable, and easier to debug.

MGU Library Connection
  • Web Development: HTML, CSS, and JavaScript — Appendix G: JavaScript Browser Reference
  • Web Development: HTML, CSS, and JavaScript — Appendix M: Glossary
  • Companion resource: Event Listener Guide
  • Companion resource: JavaScript DOM Guide (preview for Chapter 22)
  • Companion resource: Accessibility Checklist
  • Companion resource: Browser Developer Tools Guide
  • Next chapter: Chapter 22 — The DOM: Reading and Changing the Page