Handling events in JavaScript

1. Event Handlers

Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key’s state so that you’d catch it before it’s released again. It would be dangerous to per­form other time-intensive computations since you might miss a keypress.

Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.

Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program notic­ing the event will cause the software to feel unresponsive. This approach is called polling. Most programmers prefer to avoid it.

A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as handlers for specific events.

<p>Click this document to activate the handler.</p>

<script>

window.addEventListener(“click”, () => {

console.log(“You knocked?”);

});

</script>

The window binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its addEventListener method registers the second argument to be called when­ever the event described by its first argument occurs.

2. Events and DOM Nodes

Each browser event handler is registered in a context. In the previous exam­ple we called addEventListener on the window object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object they are registered on.

<button>Click me</button>

<p>No handler here.</p>

<script>

let button = document.querySelector(“button”);

button.addEventListener(“click”, () => {

console.log(“Button clicked.”);

});

</script>

That example attaches a handler to the button node. Clicks on the but­ton cause that handler to run, but clicks on the rest of the document do not.

Giving a node an onclick attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with on in front of it.

But a node can have only one onclick attribute, so you can register only one handler per node that way. The addEventListener method allows you to add any number of handlers so that it is safe to add handlers even if there is already another handler on the element.

The removeEventListener method, called with arguments similar to addEventListener, removes a handler.

<button>Act-once button</button>

<script>

let button = document.querySelector(“button”);

function once() {

console.log(“Done.”);

button.removeEventListener(“click”, once);

}

button.addEventListener(“click”, once);

</script>

The function given to removeEventListener has to be the same function value that was given to addEventListener. So, to unregister a handler, you’ll want to give the function a name (once, in the example) to be able to pass the same function value to both methods.

3. Event Objects

Though we have ignored it so far, event handler functions are passed an argument: the event object. This object holds additional information about the event. For example, if we want to know which mouse button was pressed, we can look at the event object’s button property.

<button>Click me any way you want</button>

<script>

let button = document.querySelector(“button”);

button.addEventListener(“mousedown”, event => {

if (event.button == 0) {

console.log(“Left button”);

}

else if (event.button == 1) {

console.log(“Middle button”);

}

else if (event.button == 2) {

console.log(“Right button”);

}

});

</script>

The information stored in an event object differs per type of event. We’ll discuss different types later in the chapter. The object’s type property always holds a string identifying the event (such as “click” or “mousedown”).

4. Propagation

For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event.

But if both the paragraph and the button have a handler, the more spe­cific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s par­ent node and on to the root of the document. Finally, after all handlers

registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

At any point, an event handler can call the stopPropagation method on the event object to prevent handlers further up from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

The following example registers “mousedown” handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls stopPropagation, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.

<p>A paragraph with a <button>button</button>.</p>

<script>

let para = document.querySelector(“p”);

let button = document.querySelector(“button”);

para.addEventListener(“mousedown”, () => {

console.log(“Handler for paragraph.”);

});

button.addEventListener(“mousedown”, event => {

console.log(“Handler for button.”);

if (event.button == 2) event.stopPropagation();

});

</script>

Most event objects have a target property that refers to the node where they originated. You can use this property to ensure that you’re not acciden­tally handling something that propagated up from a node you do not want to handle.

It is also possible to use the target property to cast a wide net for a spe­cific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the target property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.

<button>A</button>

<button>B</button>

<button>C</button>

<script>

document.body.addEventListener(“click”, event => {

if (event.target.nodeName == “BUTTON”) {

console.log(“Clicked”, event.target.textContent);

}

});

</script>

5. Default Actions

Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

For most types of events, the JavaScript event handlers are called before the default behavior takes place. If the handler doesn’t want this normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:

<a href=”https://developer.mozilla.org/”>MDN</a>

<script>

let link = document.querySelector(“a”);

link.addEventListener(“click”, event => {

console.log(“Nope.”);

event.preventDefault();

});

</script>

Try not to do such things unless you have a really good reason to. It’ll be unpleasant for people who use your page when expected behavior is broken.

Depending on the browser, some events can’t be intercepted at all.

On Chrome, for example, the keyboard shortcut to close the current tab (ctrl-W or command-W) cannot be handled by JavaScript.

Source: Haverbeke Marijn (2018), Eloquent JavaScript: A Modern Introduction to Programming, No Starch Press; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *