Learn JavaScript: Novice to Ninja

Chapter 7: Events

We saw in the last chapter how the DOM is an interface that allows you to use JavaScript to interact with a web page. Events are another part of the DOM and they are what provides the link between the web page and user interactions. Every time a user interacts with a web page, such as clicking on a link, pressing a key, or moving a mouse, an event occurs that our program can detect and then respond to.

In this chapter, we’ll cover the following topics:

  • Introduction to events
  • Adding event listeners
  • The event object
  • Mouse, keyboard and touch events
  • Removing event listeners
  • Stopping default behavior
  • Event propagation
  • Project – we’ll add a ‘start’ button that can be clicked on to start the game

Event Listeners

Imagine you’re waiting for a really important email that you need to act upon as soon as it arrives, but you also have some JavaScript programming to do. You could keep checking your email every couple of minutes to see if the message has arrived, but this will cause lots of interruptions to your progress creating the next killer app. Not to mention you might be unable to check your email at the exact moment the message arrives, so it might be too late to act upon. The obvious answer is to set up a notification that will pop up as soon as the email arrives. You can happily program away without the distraction of constantly checking your email, because you’ll receive a satisfying ‘ping’ as soon as the email arrives.

Event listeners in JavaScript work in much the same way. They are like setting a notification to alert you when something happens. Instead of the program having to constantly check to see if an event has occurred, the event listener will let it know when the event happens, and the program can then respond appropriately. This allows the program to continue with other tasks while it waits for the event to happen.

For example, say in your program you want something to happen when a user clicks on the page. The code to check if a user has clicked might look like the example below (JavaScript doesn’t actually work like this, so this code would fail to work, although it is the way some programming languages work):

if (click) {    doSomething();    } else {    // carry on with rest of the program}

The problem with this approach is that the program would have to keep returning to this if block to check if the click had happened. It’s a bit like having to check your email every few minutes. This is known as ablockingapproach to programming because checking for the click is blocking the rest of the program from running.

JavaScript, on the other hand, uses anon-blockingapproach that usesevent listenersto listen out for any clicks on the page. Every time the page is clicked, a callback function will be called. So the program can continue processing the rest of the code while it’s waiting for the click event to happen.

The following code can be used to attach an event listener to the document that fires when the user clicks anywhere on the page:

document.body.addEventListener("click", doSomething);

Event listeners are added to elements on the page and are part of the DOM that we met in the last chapter. In the example above, the event listener has been added to the document’s body element. It will call the function doSomething() when any part of the page is clicked on. Until that happens, the program will continue to run the rest of the code.

The click Event

The click event occurs when a user clicks with the mouse, presses the Enter key, or taps the screen, making it a very useful all-round event covering many types of interaction.

Inline Event Handlers

The original way of dealing with events in the browser was to use inline attributes that were added directly into the markup. Here’s an example that adds an onclick event handler to a paragraph element:

<p onclick="console.log('You Clicked Me!')">Click Me</p>

The JavaScript code inside the quote marks will be run when a user clicks on the paragraph. This method will still work in modern browsers, but it isn’t recommended for a number of reasons:

  • The JavaScript code is mixed up with the HTML markup, breaking the concept of unobtrusive JavaScript, which advocates that JavaScript code should be kept out of the HTML.
  • Only one event handler for each event-type can be attached to an element.
  • The code for the event handlers is hidden away in the markup, making it difficult to find where these events have been declared.
  • The JavaScript code has to be entered in a string, so you need to be careful when using apostrophes and quote marks.

For these reasons, inline event handlers are best avoided, and have only been included here for the sake of completion, and in case you see them in some code examples online.

Older Event Handlers

Another method is to use the event handler properties that all node objects have. These can be assigned to a function that would be invoked when the event occurred. The following example would cause a message to be logged to the console when the page is clicked:

document.onclick = function (){ console.log('You clicked on the page!'); }

This method is an improvement on the inline event handlers as it keeps the JavaScript out of the HTML markup. It is also well-supported and will work in almost all browsers. Unfortunately, it still has the restriction that only one function can be used for each event.

Using Event Listeners

The recommended way of dealing with events, and the current standard, is to use event listeners. These were outlined in DOM level 2 and allow multiple functions to be attached independently to different events. They are supported in all modern browsers, although only in Internet Explorer from version 9 onwards.

The addEventListener() method is called on a node object, the node to which the event listener is being applied. For example, this code will attach an event listener to the document’s body:

document.body.addEventListener('click',doSomething);

The addEventListener() method can also be called without a node, in which case it is applied to the global object, usually the whole browser window.

Its first parameter is the type of event, and the second is a callback function that is invoked when the event occurs. There is also a third parameter that we’ll cover later in the chapter.

In the next example, we are adding a click event listener to the whole page (because the addEventListener method is called without a node reference preceding it), and using an anonymous function as the callback:

addEventListener('click', () => alert('You Clicked!'));

Alternatively, a named function could be declared and then referenced in the event listener:

function doSomething() {alert('You Clicked!');}addEventListener('click',doSomething);

Note that the parentheses are not placed after the function when it’s used as the argument to an event listener; otherwise, the function will actually be called when the event listener is set, instead of when the event happens!

Support in Old Versions of IE

All modern browsers now support these event listeners. Unfortunately, this has not always been the case, and older versions of Internet Explorer (version 8 and below) use a different syntax. If you need to support these browsers (and if you do, I feel for you!), John Resig hasa simple solution for creating cross-browser add and remove event functions.

Example Code

To test the examples in this chapter, create a file called events.html that contains the following HTML code. This includes some paragraph elements to which we’ll attach event listeners throughout the chapter:

<!doctype html><html lang='en'><head><meta charset='utf-8'><title>Events Examples</title><style>    p {        width: 200px;        height: 200px;        margin: 10px;        background-color: #ccc;        float: left;    }    .highlight {        background-color: red;    }</style></head><body><p id='click'>Click On Me</p><p id='dblclick'>Double Click On Me</p><p id='mouse'>Hover On Me</p><script src='main.js'></script></body></html>

Now add the following code to a file called main.js that is saved in the same folder as events.html :

function doSomething(){    console.log('Something Happened!');}addEventListener('click', doSomething);

Now try opening events.html in a browser with the console open and click anywhere on the page. You should see this message in the console:

<< Something Happened!

The Event Object

Whenever an event handler is triggered by an event, the callback function is called. This function is automatically passed an event object as a parameter that contains information about the event.

To see an example of this, change the doSomething() function in the main.js file to this:

function doSomething(event){    console.log(event.type);}

Now refresh the events.html page in the browser and try clicking again. You should see the following appear in the console every time you click:

<< click

In the example, the type property is used to tell us that the type of event logged was a click event.

Parameter Naming

The parameter does not have to be called event . It can be given any legal variable name, although calling it event can make it easier to read the code. Many developers often abbreviate it to just e .

Types of Event

The type property returns the type of event that occurred, such as click in the previous example. The different types of events will be discussed in the next section.

The Event Target

The target property returns a reference to the node that fired the event. If you change the doSomething() function to the following, it will show a message in the console telling us the node that was clicked on:

function doSomething(event){    console.log(event.target);}

For example, if you click on one of the paragraphs, you should see something similar to the following in the console:

<< <p id='click'>Click On Me</p>

Coordinates of an Event

There are a variety of ways to find the position of where a mouse event occurs.

The screenX and screenY properties show the number of pixels from the left and top of the screen respectively where the event took place.

The clientX and clientY properties show the number of pixels from the left and top of the client that is being used (usually the browser window).

The pageX and pageY properties show the number of pixels from the left and top, respectively, where the event took place in thedocument. This property takes account of whether the page has been scrolled.

All these event properties are similar, but subtly different. They are useful for finding out the place where a click happened or the position of the mouse cursor. To see the coordinates that are returned for these properties, change the doSomething() function to the following:

function doSomething(event){    console.log(`screen: (${event.screenX},${event.screenY}), page: (${event.pageX},${event.pageY}), client: (${event.screenX},${event.screenY})`)}

Types of Events

There are several types of events, ranging from when a video has finished playing to when a resource has completed downloading. You can see afull list on the Events page of the Mozilla Developer Network.

In this section, we’re going to focus on some of the more common events that occur using the mouse, the keyboard and touch.

Mouse Events

We have already seen the click event that occurs when a mouse button is clicked. There are also the mousedown and mouseup events. These both occurbeforeclick event is fired.

To see this in action, remove all the code in main.js and replace it with the following:

const clickParagraph = document.getElementById('click');clickParagraph.addEventListener('click',() => console.log('click') );clickParagraph.addEventListener('mousedown',() => console.log('down') );clickParagraph.addEventListener('mouseup',() => console.log('up') );

Try clicking anywhere on the page and you should see all three events fire in the following order:

<< mousedownmouseupclick

There is also the dblclick event, which occurs when the user doubleclicks on the element to which the event listener is attached. To see an example of this, we’ll attach an event listener to the second paragraph in our example (with an ID of ‘dblclick’). Add the following code to main.js :

const dblclickParagraph = document.getElementById('dblclick');dblclickParagraph.addEventListener('dblclick', highlight);function highlight(event){    event.target.classList.toggle('highlight');}

Now if you double-click on the second paragraph, it should change color as the class of highlight is toggled on and off.

Using click and doubleclick On The Same Element

You should be very cautious of attaching both a click and doubleclick event to the same element. This is because it’s impossible to tell if a click is the first click of a doubleclick or just a single click. This means that a doubleclick event willalwayscause the click event to fire.

The mouseover event occurs when the mouse pointer is placed over the element to which the event listener is attached, while the mouseout event occurs when the mouse pointer moves away from an element. This example uses both the mouseover and mouseout events to change the color of the third paragraph (with an ID of ‘mouse’) when the mouse pointer hovers over it, and back again when it moves away from the paragraph:

const mouseParagraph = document.getElementById('mouse');mouseParagraph.addEventListener('mouseover', highlight);mouseParagraph.addEventListener('mouseout', highlight);

The mousemove event occurs whenever the mouse moves. It will only occur while the cursor is over the element to which it’s applied. The following line of code creates a log in the console whenever the mouse moves over the third paragraph:

mouseParagraph.addEventListener('mousemove', () =>  console.log('You Moved!') );

Keyboard Events

Three events that occur when a user presses a key are: keydownkeypress and keyup . When a user presses a key, the events occur in that order. They are not tied to any particular key, although the information about which key was pressed is a property of the event object.

  1. The keydown event occurs when a key is pressed and willcontinue to occurif the key is held down.
  2. The keypress event occurs after a keydown event but before a keyup event. The keypress event only occurs for keys that produce character input (plus the ‘Delete’ key). This means that it’s the most reliable way to find out the character that was pressed on the keyboard.
  3. The keyup event occurs when a key is released.

To understand the differences in these events, it is important to distinguish between a physicalkeyon the keyboard and acharacterthat appears on the screen. The keydown event is the action of pressing a key, whereas the keypress event is the action of a character being typed on the screen.

To see an example of this add the following to main.js :

addEventListener('keydown',highlight);

Now refresh the page and try pressing a key. It should result in the whole document changing color, because event listener was applied to the whole document. If you hold a key down, the event will continue to fire, creating a psychedelic effect on the page.

To see the keyup event working, add the code that uses an anonymous arrow function to show the exact time the key was released in the console:

addEventListener('keyup', (event) => console.log(`You stopped pressing the key on ${new Date}`));

Each of these keyboard events have an key property that returns the printed representation of the key that was pressed, if it has one.

To see this in action, add the following code to main.js :

addEventListener('keypress', (event) => console.log(`You pressed the ${event.key} character`));

Now when you press a key, you should see a message similar to this in the console:

<< You pressed the j character

Supporting Older Browsers

The key property has good support in modern browsers, but if you need to support older browsers, then a library such askeycode.jswill come in handy as it normalizes the key codes returned. The jQuery library also has a which propertythat does this as well.

Modifier Keys

Pressing the modifier keys such asShift,Ctrl,Altandmeta(Cmdon Mac) will fire the keydown and keyup events, but not the keypress event as they don’t produce any characters on the screen.

The name of the modifier key is still returned by the key property. To see this, edit the event listener we just used to listen for a keydown event instead:

addEventListener('keydown', (event) => console.log(`You pressed the ${event.key} character`));

Now try pressing a modifier key:

<< "You pressed the Control character"

All event objects also contains information about whether a modifier key was held down when the key event occurred. The shiftKeyctrlKeyaltKey , and metaKey are all properties of the event object and return true if the relevant key was held down. For example, the following code will check to see if the user pressed theCkey while holding down theCtrlkey:

addEventListener('keydown', (event) => {if (event.key === 'c' && event.ctrlKey) {        console.log('Action canceled!');    }});

The following code checks to see if theShiftkey was held down when the mouse was clicked:

addEventListener('click', (event) => {    if (event.shiftKey) {        console.log('A Shifty Click!');    }});

Modifying Default Behavior

Modifier keys can often already have a purpose assigned in the browser or operating system. And although it’s possible to prevent the default behavior in the browser (see later in this chapter), it’s not considered best practice to do so.

Touch Events

Many modern devices now support touch events. These are used on smartphones and tablets, as well as touch-screen monitors, satellite navigators and trackpads. Touch events are usually made with a finger, but can also be by stylus or another part of the body. There are a number of touch events that cover many types of touch interactions.

It’s important to support mouse events as well as touch events, so non-touch devices are also supported. With so many different devices these days, you can’t rely on users using just touch or a mouse. In fact, some devices, such as touchscreen laptops, support both mouse and touch interactions.

The touchstart event occurs when a user initially touches the surface.

Using the touchstart Event

Be careful when using the touchstart event as it fires as soon as a user touches the screen. They may be touching the screen because they want to zoom in or swipe, and a touchstart event listener could prevent them from doing this.

The click event is often a much safer option as it still fires when the screen is touched, but there’s a slight delay of 300ms, allowing the user time to perform another action with the device. The click event can be thought of as a “tap” in the context of a touch event.

The touchend event occurs when a user stops touching the surface:

addEventListener('touchend', () => console.log('Touch stopped');

The touchmove event occurs after a user has touched the screen then moves around without leaving. It will continue to occur as long as the user is still touching the screen, even if they leave the element to which the event listener is attached.

The touchenter event occurs when a user has already started touching the surface, but then passes over the element to which the event listener is attached.

The touchleave event occurs when the user is still touching the surface, but leaves the element to which the event listener is attached.

The touchcancel event occurs when a touch event is interrupted, such as a user’s finger moving outside the document window, or too many fingers being used at once. A pop-up dialog will also cancel a touch event.

What About Swiping?

There are no ‘swipe’ events. These need to be created by using a combination of touchstarttouchmove , and touchleave events that monitor the distance and direction moved from start to finish of a touch event.

There were proposals for gesture eventsthat may be supported in the future, but it seems they are not scheduled to be part of the specification anytime soon.

If you need to implement gestures, it’s probably a good idea to use a library such asHammer.JSorzingtouchthat makes events such as swipe, pinch and rotate easy to implement.

Touch Event Properties

Because it’s possible to touch a surface many times at once, touch event objects have a property called touches . This is a list of touch objects that represents all the touches taking place on that device. It has a length property that tells you how manytouch points(usually the user’s fingers, but could be a stylus) are in contact with the surface. Each touch object in the list can be accessed using index notation. For example, if a user touches the screen with two fingers, events.touches.length would return 2 . The first touch object can be accessed using events.touches[0] and the second using events.touches[1] .

Each touch object has a number of properties, many similar to the event object, such as touch.screenX and touch.screenY to find the coordinates of the touch point. They have other properties such as touch.radiusX and touch.radiusY , which give an indication of the area covered by the touch, and touch.force , which returns the amount of pressure being applied by the touch as a value between 0 and 1 .

Each touch object has a touch.identifier property, a unique ID that can be used to ensure you are dealing with the same touch.

Use Touch Events With Caution

Touch events are complex and difficult to implement. Many of the properties and methods mentioned above are still marked as being experimental and not widely implemented in browsers.

Removing Event Listeners

An event listener can be removed using the removeEventListener() method. To see an example, add this line to events.html :

<p id='once'>A One Time Thing...</p>

Now add the following code to main.js :

const onceParagraph = document.getElementById('once');onceParagraph.addEventListener('click', remove);function remove(event) {    console.log('Enjoy this while it lasts!');    onceParagraph.style.backgroundColor = 'pink';    onceParagraph.removeEventListener('click',remove);}

This adds a click event listener to a paragraph element, but then removes it in the callback function named remove . This means it will only be called once (try clicking on it again and nothing happens).

Using Anonymous Functions

Note that you shouldn’t use anonymous functions as an argument to addEventListener() if you want to remove it later. This is because there needs to be a reference to the same function name in the arguments of removeEventListener() .

Stopping Default Behavior

Some elements have default behavior associated with certain events. For example, when a user clicks on a link, the browser redirects to the address in the href attribute and a form is submitted when the user clicks on theSubmitbutton.

 preventDefault() is a method of the event object that can be used inside the callback function to stop the default behavior happening. To see an example, add the following line to the events.html file:

<p>    <a id='broken' href='https://sitepoint.com'>Broken Link</a></p>

Then add the following event listener inside the main.js file:

const brokenLink = document.getElementById('broken');brokenLink.addEventListener('click',(event) => {    event.preventDefault();    console.log('Broken Link!');});

This will stop the page redirecting to the page specified in the href #39; attribute, and show a message in the console instead.

Think Carefully Before Using preventDefault()

Make sure you think carefully before using preventDefault() to change default behavior. Users will expect certain behaviors, and preventing them may cause confusion.

Some events do not allow the default behavior to be prevented. This can vary from browser to browser, but each event object has a property called cancellable that returns false if it cannot be prevented.

You can also see if the default behavior has been prevented by checking the defaultPrevented property.

Event Propagation

When you click on an element, you are actually clicking on all the elements it’s nested inside of. To illustrate this, add the following piece of HTML to the events.html file:

<ul id='list'>    <li>one</li>    <li>two</li>    <li>three</li></ul>

If you click on one of the <li> elements, you’re also clicking on the <ul><body> and <html> elements. An event is said topropagateas it moves from one element to another.

Event propagation is the order that the events fire on each element. There are two forms of event propagation: bubbling and capturing.

Bubbling is when the event fires on the element clicked on first, then bubbles up the document tree, firing an event on each parent element until it reaches the root node.

Capturing starts by firing an event on the root element, then propagates downwards, firing an event on each child element until it reaches the target element that was clicked on.

Capturing vs. bubbling

The capturing model was originally implemented by Netscape, and the bubbling model was implemented in Microsoft browsers back in the ‘bad old days’ of the Browser Wars. The W3C sensibly came down in the middle and allowed developers to decide which method they prefer to use.

Bubbling

The default behavior is bubbling, which we can see happen if we add the following code to main.js :

ulElement = document.getElementById('list');liElement = document.querySelector('#list li');ulElement.addEventListener('click', (event) =>console.log('Clicked on ul') );liElement.addEventListener('click', (event) =>console.log('Clicked on li') );

Now try clicking on the first <li> element in the list. There should be a message in the console saying “Clicked on li” because this was the target element. The event then bubbles up to the parent <ul> element and displays a message in the console saying “Clicked on ul”. The event will continue to bubble all the way to the root HTML element, but nothing will happen because none of the other elements had event listeners attached to them.

If you click on the second or third <li> elements in the list you will only see the message “Clicked on ul”. This is because, even though these elements don’t have an event listener attached to them, the click still bubbles up and is captured by the <ul> element thatdoeshave an event listener attached.

Capturing

The addEventListener() method has a third parameter, which is a boolean value that specifies whether capturing should be used or not. It defaults to false , which is why bubbling happens by default. There may be instances when you would rather capture the events instead; for example, you might want events on outer elements to fire before any events fire on the element that was actually clicked on.

To implement capturing instead, change the code to the following:

ulElement.addEventListener('click', (event) =>console.log('Clicked on ul'),true);liElement.addEventListener('click', (event) =>console.log('Clicked on li'),true);

Now if you click on the first list item, “Clicked on ul” will be logged to the console first. The events then propagate downwards to the child <li> element, so “Clicked on li” is logged to the console next.

If you want the event to both captureandbubble, you must set a separate event handler for both cases, like so:

// capturingulElement.addEventListener('click', (event) =>console.log('Clicked on ul'),true);liElement.addEventListener('click', (event) =>console.log('Clicked on li'),true);// bubblingulElement.addEventListener('click', (event) =>console.log('Clicked on ul'),false );liElement.addEventListener('click', (event) =>console.log('Clicked on li'),false );

Stopping the Bubbling Phase

The bubble phase can be stopped from occurring by adding the event.stopPropagation() method into the callback function. In the following example, the event will fail to propagate as the third argument is false , which stops capturing, and the event.stopPropagation() method is called, which stops bubbling:

liElement.addEventListener('click', (event) => {console.log('clicked on li');event.stopPropagation(); }, false);

Now clicking on the first <li> element will only log one message, since the click event will not propagate to the <ul> element.

Be Careful Not to Stop Other Event Listeners Firing

Be very wary of using the stopPropagation() method to stop the bubble phase occurring. There may be other event listeners attached to elements further up the chain that won’t fire as a result.

You can read more about event propagation in thisarticle on SitePoint.

Event Delegation

Event delegation can be used to attach an event listener to a parent element in order to capture events that are triggered by its child elements.

Let’s look at the list items in our example:

<ul id='list'>    <li>one</li>    <li>two</li>    <li>three</li></ul>

If we wanted to attach event listeners to all the <li> tags so they were highlighted when clicked on, it would need more code to add a separate event listener to each element. In this case, there isn’t much difference, but imagine if you had a list of 100 elements!

A better way is to attach the event listener to the parent <ul> element, then use the target property to identify the element that was clicked on. Add the following to main.js to see this in action (remember that the highlight() function used the target property):

ulElement.addEventListener('click',highlight);

Now clicking on any list item will highlight that list item as if it was the target of the click event.

This is a useful method if you are adding extra list elements to the DOM dynamically. Any new list elements that are a child of the <ul> element will automatically inherit this event listener, saving you from having to add an event listener every time a new list item is added.

Quiz Ninja Project

Now that we’ve reached the end of the chapter, it’s time to add some events to our Quiz Ninja Project. We’re going to add a button that can be clicked on to start the game.

To start, add this line of code to index.html , just before the closing <body> tag:

<button id='start'>Click to Start</button>

This will add a button to the markup. Now we need a reference to it in main.js . Add the following line of property to the view object:

start: document.getElementById('start'),

Now we need to attach a ‘click’ event listener to the button that will start the game when the button is clicked. Add the following code to the end of main.js :

view.start.addEventListener('click', () => game.start(quiz), false);

We’re also going to add a couple of utility functions that will show and hide elements on a page. These also go in view object as they are only concerned with the view:

show(element){    element.style.display = 'block';},hide(element){    element.style.display = 'none';}

These work by simply changing the style.display property to none to hide an element, and block to display it.

We can use these to make the start button disappear while the game is in progress, then reappear once the game has finished. Add the following line of code to the game.start() method:

view.hide(view.start);

Then add the following line to the game.gameOver() method:

view.show(view.start);

And that’s it ― hopefully you can see that it wasn’t too hard to add a start button with its own event listener attached; especially since the function it invoked was already written. Open index.html and have a go at playing the game. It should look similar to the below.

Our start button

You can see a live example onCodePen.

Chapter Summary

  • Events occur when a user interacts with a web page.
  • An event listener is attached to an element, then invokes a callback function when the event occurs.
  • The event object is passed to the callback function as an argument, and contains lots of properties and methods relating to the event.
  • There are many types of event, including mouse events, keyboard events, and touch events.
  • You can remove an event using the removeEventListener method.
  • The default behavior of elements can be prevented using the preventDefault() function.
  • Event propagation is the order the events fire on each element.
  • Event delegation is when an event listener is added to a parent element to capture events that happen to its children elements.

In the next chapter, we’ll look at how we can use forms to enter information into the browser, and use events to process that information.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16