Chapter 14

Handling Events

You have power over your mind—not
outside events. Realize this, and you will find strength.

Marcus Aurelius, Meditations

Some programs work with direct user input, such as mouse and
keyboard interaction. The timing and order of such input can’t be
predicted in advance. This requires a different approach to control
flow than the one we have used so far.

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
perform other time-intensive computations since you might miss a
keypress.

That is how such input was handled on primitive machines. A step
up 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 noticing the event will cause the
software to feel unresponsive. This approach is called polling.
Most programmers avoid it whenever possible.

A better mechanism is for
the underlying system to give our code a chance to react
to events as they occur. Browsers do this by allowing us to register
functions as handlers for specific events.

The addEventListener
function registers its second argument to be called whenever the event
described by its first argument occurs.

Events and DOM nodes

Each browser
event handler is registered in a context. When you call
addEventListener as shown previously, you are calling it as a method on the
whole window because in the browser the global scope is
equivalent to the window object. Every DOM element has its own
addEventListener method, which allows you to listen specifically on
that element.

The example attaches a handler
to the button node. Thus, clicks on the button cause that handler to
run, whereas clicks on the rest of the document do not.

Giving a node an onclick
attribute has a similar effect. But a node has 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
you can’t accidentally replace a handler that has already been
registered.

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

To be able to unregister a handler function, we
give it a name (such as once) so that we
can pass it to both addEventListener and removeEventListener.

Event objects

Though we have ignored it in
the previous examples, event handler functions are passed an argument:
the event object. This object gives us 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 which property.

The information stored in an event
object differs per type of event. We’ll discuss various types later
in this chapter. The object’s type property always holds a string
identifying the event (for example "click" or "mousedown").

Propagation

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

But if both the paragraph and the button have a
handler, the more specific 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 parent 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.

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 accidentally 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 specific 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.

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 is performed.
If the handler doesn’t want the 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:

Despite its name, "keydown" fires not only
when the key is physically pushed down. When a key is pressed and
held, the event fires again every time the key repeats.
Sometimes—for example if you want to increase the acceleration of a
game character when an arrow key is pressed and decrease it again
when the key is released—you have to be careful not to increase it
again every time the key repeats or you’d end up with unintentionally
huge values.

The previous example looked at the
keyCode property of the event object. This is how you can identify
which key is being pressed or released. Unfortunately, it’s not
always obvious how to translate the numeric key code to an actual
key.

For letter and number keys,
the associated key code will be the Unicode character code
associated with the (uppercase) letter or number printed on the key.
The charCodeAt method on strings gives us a way to find this
code.

Other keys have less predictable key codes. The best way to find
the codes you need is usually by experimenting—register a key event
handler that logs the key codes it gets and press the key you are
interested in.

Modifier keys
such as Shift, Ctrl, Alt, and Meta (Command on Mac) generate key
events just like normal keys. But when looking for key combinations,
you can also find out whether these keys are held down by looking
at the shiftKey, ctrlKey, altKey, and metaKey properties of
keyboard and mouse events.

The
"keydown" and "keyup" events give you information about the
physical key that is being pressed. But what if you are interested in
the actual text being typed? Getting that text from key codes is
awkward. Instead, there exists another event, "keypress", which
fires right after "keydown" (and repeated along with "keydown"
when the key is held) but only for keys that produce character input.
The charCode property in the event object contains a code that can
be interpreted as a Unicode character code. We can use the
String.fromCharCode function to turn this code into an
actual single-character string.

The DOM node where
a key event originates depends on the element that has focus when
the key is pressed. Normal nodes cannot have focus (unless you give
them a tabindex attribute), but things such as links, buttons, and
form fields can. We’ll come back to form fields in
Chapter 18. When nothing in particular has
focus, document.body acts as the target node of key events.

Mouse clicks

Pressing a
mouse button also causes a number of events to fire. The
"mousedown" and "mouseup" events are similar to "keydown" and
"keyup" and fire when the button is pressed and released.
These will happen on the DOM nodes that are immediately below the
mouse pointer when the event occurs.

After the "mouseup" event, a "click" event
fires on the most specific node that contained both the press and the
release of the button. For example, if I press down the mouse button
on one paragraph and then move the pointer to another paragraph and
release the button, the "click" event will happen on the element
that contains both those paragraphs.

If two clicks happen close
together, a "dblclick" (double-click) event also fires, after the
second click event.

To get precise information about the place where a mouse
event happened, you can look at its pageX and pageY properties,
which contain the event’s coordinates (in pixels) relative to the
top-left corner of the document.

The following implements a primitive drawing program. Every
time you click the document, it adds a dot under your mouse
pointer. See Chapter 19 for a less primitive
drawing program.

The clientX and clientY properties are
similar to pageX and pageY but relative to the part of the
document that is currently scrolled into view. These can be useful
when comparing mouse coordinates with the coordinates returned by
getBoundingClientRect, which also returns viewport-relative
coordinates.

Mouse motion

Every time the mouse pointer moves, a
"mousemove" event fires. This event can be used to track the
position of the mouse. A common situation in which this is useful is
when implementing some form of mouse-dragging functionality.

As an example, the following program displays a
bar and sets up event handlers so that dragging to the left or right
on this bar makes it narrower or wider:

Note that the "mousemove"
handler is registered on the whole window. Even if the mouse goes
outside of the bar during resizing, we still want to update its size
and stop dragging when the mouse is released.

We must stop resizing the
bar when the mouse button is released. Unfortunately, not all browsers
give "mousemove" events a meaningful which property. There is a
standard property called buttons, which provides similar
information, but that is also not supported on all browsers.
Fortunately, all major browsers support either buttons or which,
so the buttonPressed function in the example first tries buttons,
and falls back to which when that isn’t available.

Whenever the mouse pointer
enters or leaves a node, a "mouseover" or "mouseout" event
fires. These two events can be used, among other things, to create
hover effects, showing or styling something when the mouse is over
a given element.

Unfortunately, creating such an effect is not
as simple as starting the effect on "mouseover" and ending it on
"mouseout". When the mouse moves from a node onto one of its
children, "mouseout" fires on the parent node, though the mouse
did not actually leave the node’s extent. To make things worse, these
events propagate just like other events, and thus you will also
receive "mouseout" events when the mouse leaves one of the child nodes of the node on which the handler is registered.

To work around this problem, we can use the relatedTarget
property of the event objects created for these events. It tells us,
in the case of "mouseover", what element the pointer was over
before and, in the case of "mouseout", what element it is going to.
We want to change our hover effect only when the relatedTarget is
outside of our target node. Only in that case does this event actually
represent a crossing over from outside to inside the node (or the
other way around).

The isInside function follows the given node’s parent links until it
either reaches the top of the document (when node becomes null) or
finds the parent we are looking for.

I should add that a hover effect like this can be much more easily
achieved using the CSS pseudoselector:hover, as the next
example shows. But when your hover effect involves doing something
more complicated than changing a style on the target node, you must use the trick
with "mouseover" and "mouseout" events.

Scroll events

Whenever an
element is scrolled, a "scroll" event fires on it. This has
various uses, such as knowing what the user is currently looking at
(for disabling off-screen animations or sending spy reports to
your evil headquarters) or showing some indication of progress (by
highlighting part of a table of contents or showing a page number).

The following example draws a progress bar in the top-right corner of
the document and updates it to fill up as you scroll down:

Giving an element
a position of fixed acts much like an absolute position but
also prevents it from scrolling along with the rest of the document.
The effect is to make our progress bar stay in its corner. Inside
it is another element, which is resized to indicate the current
progress. We use %, rather than px, as a unit when setting the
width so that the element is sized relative to the whole bar.

The global innerHeight variable gives us the height of
the window, which we have to subtract from the total scrollable
height—you can’t keep scrolling when you hit the bottom of the
document. (There’s also an innerWidth to go along with
innerHeight.) By dividing pageYOffset, the current scroll
position, by the maximum scroll position and multiplying by 100,
we get the percentage for the progress bar.

Calling preventDefault on a scroll event
does not prevent the scrolling from happening. In fact, the event
handler is called only after the scrolling takes place.

Focus events

When an element
gains focus, the browser fires a "focus" event on it. When it
loses focus, a "blur" event fires.

Unlike the events discussed earlier, these two
events do not propagate. A handler on a parent element is not notified
when a child element gains or loses focus.

The following example
displays help text for the text field that currently has
focus:

The window object will receive
"focus" and "blur" events when the user moves from or to the
browser tab or window in which the document is shown.

Load event

When a page finishes loading,
the "load" event fires on the window and the document body
objects. This is often used to schedule initialization actions
that require the whole document to have been built. Remember that
the content of <script> tags is run immediately when the tag is
encountered. This is often too soon, such as when the script needs
to do something with parts of the document that appear after the
<script> tag.

Elements such as images
and script tags that load an external file also have a "load" event
that indicates the files they reference were loaded. Like the
focus-related events, loading events do not propagate.

When a page is closed or navigated away from (for example by
following a link), a "beforeunload" event fires. The main use of
this event is to prevent the user from accidentally losing work by
closing a document. Preventing the page from unloading is not, as you
might expect, done with the preventDefault method. Instead, it is
done by returning a string from the handler. The string will be used
in a dialog that asks the user if they want to stay on the page or
leave it. This mechanism ensures that a user is able to leave the
page, even if it is running a malicious script that would prefer to
keep them there forever in order to force them to look at dodgy
weight loss ads.

Script execution timeline

There are various
things that can cause a script to start executing. Reading a
<script> tag is one such thing. An event firing is another.
Chapter 13 discussed the
requestAnimationFrame function, which schedules a function to be
called before the next page redraw. That is yet another way in which a
script can start running.

It is important to
understand that even though events can fire at any time, no two
scripts in a single document ever run at the same moment. If a script
is already running, event handlers and pieces of code scheduled in
other ways have to wait for their turn. This is the reason why a
document will freeze when a script runs for a long time. The browser
cannot react to clicks and other events inside the document because
it can’t run event handlers until the current script finishes running.

Some programming environments do
allow multiple threads of execution to run at the same time.
Doing multiple things at the same time can be used to make a program
faster. But when you have multiple actors touching the same parts of
the system at the same time, thinking about a program becomes at least
an order of magnitude harder.

The fact that JavaScript programs do only one thing at a time makes
our lives easier. For cases where you really do want to do some
time-consuming thing in the background without freezing the page,
browsers provide something called web workers. A worker is an
isolated JavaScript environment that runs alongside the main
program for a document and can communicate with it only by sending
and receiving messages.

Assume we have the following code in a file called code/squareworker.js:

The postMessage function
sends a message, which will cause a "message" event to fire in the
receiver. The script that created the worker sends and receives
messages through the Worker object, whereas the worker talks to the
script that created it by sending and listening directly on its
global scope—which is a new global scope, not shared with the
original script.

Setting timers

The setTimeout function is
similar to requestAnimationFrame. It schedules another function to
be called later. But instead of calling the function at the next
redraw, it waits for a given amount of milliseconds. This page
turns from blue to yellow after two seconds:

Debouncing

Some types of events have the potential to fire
rapidly, many times in a row (the "mousemove" and "scroll" events,
for example). When handling such events, you must be careful not to do
anything too time-consuming or your handler will take up so much time
that interaction with the document starts to feel slow and choppy.

If you do need to do something nontrivial in
such a handler, you can use setTimeout to make sure you are not
doing it too often. This is usually called debouncing the event.
There are several slightly different approaches to this.

In the first example, we want to do something when the user
has typed something, but we don’t want to do it immediately for every
key event. When they are typing quickly, we just want to wait
until a pause occurs. Instead of immediately performing an action in
the event handler, we set a timeout instead. We also clear the
previous timeout (if any) so that when events occur close together
(closer than our timeout delay), the timeout from the previous event
will be canceled.

Giving an undefined value to clearTimeout or
calling it on a timeout that has already fired has no effect. Thus, we
don’t have to be careful about when to call it, and we simply do so
for every event.

We can use a slightly different pattern if we
want to space responses so that they’re separated by at least a
certain length of time but want to fire them during a series of
events, not just afterward. For example, we might want to respond to
"mousemove" events by showing the current coordinates of the mouse,
but only every 250 milliseconds.

Summary

Event handlers make it possible to detect and react to events we have
no direct control over. The addEventListener method is used to
register such a handler.

Each event has a type ("keydown", "focus", and so on) that identifies
it. Most events are called on a specific DOM element and then
propagate to that element’s ancestors, allowing handlers associated
with those elements to handle them.

When an event handler is called, it is passed an event object with
additional information about the event. This object also has methods
that allow us to stop further propagation (stopPropagation) and
prevent the browser’s default handling of the event
(preventDefault).

Scrolling can be detected with the "scroll" event, and focus changes
can be detected with the "focus" and "blur" events. When the document finishes
loading, a "load" event fires on the window.

Only one piece of JavaScript program can run at a time. Thus, event
handlers and other scheduled scripts have to wait until other scripts
finish before they get their turn.

Exercises

Censored keyboard

Between 1928
and 2013, Turkish law forbade the use of the letters Q, W, and X
in official documents. This was part of a wider initiative to stifle
Kurdish culture—those letters occur in the language used by Kurdish
people but not in Istanbul Turkish.

As an exercise in doing ridiculous
things with technology, I’m asking you to program a text field (an
<input type="text"> tag) that these letters cannot be typed into.

The solution to this
exercise involves preventing the default behavior of key events.
You can handle either "keypress" or "keydown". If either of them
has preventDefault called on it, the letter will not appear.

Identifying the letter typed requires
looking at the keyCode or charCode property and comparing that
with the codes for the letters you want to filter. In "keydown", you
do not have to worry about lowercase and uppercase letters, since it
identifies only the key pressed. If you decide to handle "keypress"
instead, which identifies the actual character typed, you have to make
sure you test for both cases. One way to do that would be this:

/[qwx]/i.test(String.fromCharCode(event.charCode))

Mouse trail

In JavaScript’s early days,
which was the high time of gaudy home pages with lots of animated
images, people came up with some truly inspiring ways to use the
language.

One of these was the “mouse trail”—a series of images that would
follow the mouse pointer as you moved it across the page.

In this exercise, I
want you to implement a mouse trail. Use absolutely positioned <div>
elements with a fixed size and background color (refer to the
code in the “Mouse Clicks”
section for an example). Create a bunch of such elements and, when the
mouse moves, display them in the wake of the mouse pointer.

There are various possible approaches here. You
can make your solution as simple or as complex as you want. A simple
solution to start with is to keep a fixed number of trail elements and
cycle through them, moving the next one to the mouse’s current
position every time a "mousemove" event occurs.

Creating the elements is best done in a
loop. Append them to the document to make them show up. To be
able to access them later to change their position, store the trail
elements in an array.

Cycling through them can be done by keeping a counter variable and adding 1 to it every time the "mousemove" event
fires. The remainder operator (% 10) can then be used to get a valid
array index to pick the element you want to position during a given
event.

Another
interesting effect can be achieved by modeling a simple physics
system. Use the "mousemove" event only to update a pair of variables
that track the mouse position. Then use requestAnimationFrame to
simulate the trailing elements being attracted to the position of the
mouse pointer. At every animation step, update their position based on
their position relative to the pointer (and, optionally, a speed that
is stored for each element). Figuring out a good way to do this is up
to you.

Tabs

A tabbed interface is a common design
pattern. It allows you to select an interface panel by choosing from
a number of tabs “sticking out” above an element.

In this exercise you’ll implement a simple tabbed
interface. Write a function, asTabs, that takes a DOM node and
creates a tabbed interface showing the child elements of that node. It
should insert a list of <button> elements at the top of the node,
one for each child element, containing text retrieved from the
data-tabname attribute of the child. All but one of the original
children should be hidden (given a display style of none), and the
currently visible node can be selected by clicking the buttons.

When it works, extend it to also style the currently active button
differently.

One pitfall you’ll
probably run into is that you can’t directly use the node’s
childNodes property as a collection of tab nodes. For one thing,
when you add the buttons, they will also become child nodes and end
up in this object because it is live. For another, the text nodes
created for the whitespace between the nodes are also in there
and should not get their own tabs.

To work around this, start
by building up a real array of all the children in the wrapper that
have a nodeType of 1.

When registering event handlers on
the buttons, the handler functions will need to know which tab element
is associated with the button. If they are created in a normal loop,
you can access the loop index variable from inside the function, but
it won’t give you the correct number because that variable will have
been further changed by the loop.

A simple workaround
is to use the forEach method and create the handler functions from
inside the function passed to forEach. The loop index, which is
passed as a second argument to that function, will be a normal local
variable there and won’t be overwritten by further iterations.