Topic 01

Events and Listeners

JavaScriptHands-on

Every script so far has run once, while the page loaded, and then stopped. A page that answers its visitor needs code that runs later: when a button is pressed, when a key is typed, when a field changes. The browser makes this possible. Each time something happens on the page, it creates an event, an object describing what happened. A script can register a listener: a function the browser calls every time a particular event happens on a particular element.

A listener works like a doorbell. You wire it once, and from then on, every time someone presses the button, the same thing happens, whether that is the first press or the fiftieth. You do not stand by the door waiting; the bell does the waiting for you. Unlike a doorbell, one button can have several listeners wired to it, and each of them runs.

How Do You Attach a Listener?

The method is addEventListener, and it takes three things: the element to watch, which is the one you call it on, the name of the event as a string, and the function to run. To try the examples on this page, add these elements to the main of index.html, and put each script at the end of js/main.js. They are practice, so remove them when you finish.

index.html
<button type="button" id="hello-button">Say hello</button>
<button type="button" id="counter">Clicks: 0</button>
<label for="name-field">Your name</label>
<input id="name-field" type="text">
<p id="echo"></p>
js/main.js
const helloButton = document.querySelector("#hello-button");

helloButton.addEventListener("click", () => {
  console.log("clicked");
});

The script finds the Say hello button and attaches a listener for the click event. Nothing happens when the page loads. Click the button and the Console shows the word clicked; click it five times and it shows five times, which Chrome's Console groups into one line with a small count beside it. The arrow function was handed to the browser, which keeps it and calls it on every click.

Why Do the Round Brackets Matter?

This is the key idea of the whole chapter. addEventListener needs the function itself, not the result of calling it. Writing the function's name hands it over. Writing the name followed by round brackets calls it immediately and hands over whatever it returns.

js/main.js
function sayHi() {
  console.log("Hi");
}

helloButton.addEventListener("click", sayHi);   // right: runs on each click
helloButton.addEventListener("click", sayHi()); // wrong: runs once, now

The first listener works: each click logs Hi. The second line is the classic mistake. It runs sayHi straight away, so Hi appears in the Console once as the page loads, before anyone clicks. sayHi returns nothing, which is undefined, so undefined is what gets registered as the listener, and the browser quietly ignores it. No error appears; the second listener simply never runs.

What Does the Event Object Hold?

When the browser calls a listener, it passes one argument: the event object. It describes what happened. type is the event's name. target is the element where it happened. For a key press, key says which key; for a click, clientX and clientY say where the pointer was, measured from the top-left corner of the visible part of the page. You name the parameter whatever you like, and event is the usual choice.

js/main.js
const counter = document.querySelector("#counter");
let count = 0;

counter.addEventListener("click", (event) => {
  count = count + 1;
  counter.textContent = `Clicks: ${count}`;
  console.log(event.type, event.target);
});

Each click adds one to count and rewrites the button's own label, so it reads Clicks: 1, then Clicks: 2, and so on. The count uses let because it changes, and it lives outside the function so that it survives from one click to the next; a name made inside the function would start again at zero every time. The Console shows the word click and, next to it, the button element itself, which you can hover to highlight on the page.

Which Events Will You Use Most?

Five events cover most of this book. click fires when an element is clicked or tapped, and also when a focused button is pressed with Enter or Space, which topic 3 explains. input fires every time the value of a field changes, so once per keystroke while someone types. change fires when a change is finished: for a text field, when the visitor leaves it after editing, or presses Enter in it; for a checkbox or a drop-down list, straight away. submit fires when a form is sent, and Chapter 10 builds on it. keydown fires when a key goes down, with the key's name in event.key.

Five everyday events
EventWhen it fires
clicka click or tap, or Enter or Space on a focused button
inputevery change to a field's value, keystroke by keystroke
changea text field is left, or Enter pressed, after editing; a checkbox is ticked or cleared
submita form is sent (Chapter 10)
keydowna key goes down; event.key names it

Here is input at work. The field's value property holds whatever is in it at this moment, and the listener copies it into the paragraph below.

js/main.js
const nameField = document.querySelector("#name-field");
const echo = document.querySelector("#echo");

nameField.addEventListener("input", (event) => {
  echo.textContent = `Hello, ${event.target.value}`;
});

Type a name into the field and the paragraph under it follows letter by letter: Hello, A, then Hello, Al, and so on. Delete characters and it follows those too. The text goes in with textContent, so if someone types an HTML tag into the field, the paragraph shows the tag as characters, exactly as the last chapter's rule requires. Change the event name to change, and the paragraph updates only when you leave the field or press Enter.

How Do You Stop Listening?

removeEventListener detaches a listener. It needs the same event name and the very same function you attached. So the function needs a name you can refer to twice, such as sayHi. An arrow function written out again creates a second, separate function, and it never matches the first.

For the common case of "only the first time", there is a shortcut: a third argument, an object with once set to true. The browser removes that listener by itself after it runs.

js/main.js
function welcome() {
  console.log("This message appears only once.");
}

helloButton.addEventListener("click", welcome, { once: true });
helloButton.removeEventListener("click", sayHi);

The first click on Say hello logs the welcome message along with clicked; later clicks log only clicked. The second line removes the sayHi listener from earlier, so clicks no longer log Hi. To see every listener an element has, select it in the Elements panel and open the Event Listeners tab among the tabs beside Styles.

One click, start to finish
Clickon the counter button
→
Listener runscount goes up by one
→
Button text updatesClicks: 1
Common Confusions
  • "addEventListener("click", sayHi()) is right." The round brackets call the function now and pass its result, usually undefined. Pass sayHi itself, without round brackets.
  • "An onclick attribute in the HTML is the same thing." It works, but it mixes behaviour into the HTML, and an element can have only one of them. addEventListener keeps the script in its file and allows any number of listeners.
  • "input and change are the same event." input fires on every change to the value; for a text field, change fires once, when the visitor leaves it or presses Enter.
Why It Matters
  • Everything interactive in the rest of the book, the filter, the theme switch, the form, starts with a listener.
  • Handing over a function without calling it is the idea behind every listener, and behind much of modern JavaScript.

Knowledge Check

What is wrong with button.addEventListener("click", save())?

  • The event name should be onclick, not click
  • It calls save now instead of on each click
  • Only arrow functions can be used as listeners
  • It needs a second listener to handle the click

Inside a click listener, what does event.target hold?

  • The name of the event, such as click
  • The function that is listening for the event
  • The position of the pointer on the screen
  • The element on which the click happened

A visitor types five letters into a text field, then presses Tab to move on. How many times do input and change fire?

  • input five times, change once as they leave
  • input once as they leave, change five times
  • both five times, once for every letter typed
  • neither, until the form itself is submitted

Why must a counter's let count = 0 sit outside the listener function?

  • Because let cannot be used in a function
  • Because the browser rejects names in listeners
  • Because inside, it would restart at zero each time
  • Because outside, the listener runs much faster

You got correct