Handling DOM Events

Introduction

Whenever we mention The DOM, we are referring to the combination of the HTML document and JavaScript. Without the DOM we would not be able to make anything happen after a page had loaded. We would not be able to change the content of a page, or respond to user actions. Modern web applications are driven by user engagement and interaction which happen spontaneously and over time. In order to handle these kinds of situations, the DOM provides us with a way to listen for events and respond to them as soon as they happen.

Setting, removing and handling DOM events is a key skill for any modern web developer. In this lesson we will learn how to handle DOM events using JavaScript.

Analogy

Kettle

An electric kettle is a simple device with only one button and one data source, an internal thermometer. When the button is pressed, the water begins to boil. When the temperature reaches boiling point, the kettle switches off. We could describe the kettle as having two states, on and off. The kettle is listening for the event of the button being pressed. When this happens, the kettle responds by switching on. The kettle is also listening for the event of the temperature reaching boiling point. When this happens, the kettle responds by switching off. We could name each event onButtonPressed and onBoilingPointReached. We use the word on to indicate that this event can happen at any time.

Common Requirements

Some of the most common situations where a web developer would need to handle events are:

  • Showing a modal / popup window when a button is clicked
  • Hiding or showing a navigation menu
  • Revealing more content when a user scrolls to the bottom of a page
  • Accepting user form input such as a password
  • Replacing broken images with a placeholder image
  • Changing night/day mode based on user preference

Anatomy of an Event

An event is an object that contains information about a specific situation that has happened. This information includes:

  • The type of event that has happened
  • The element that the event happened to
  • The time that the event happened

Some events, such as Mouse or Keyboard events also contain information about the user interaction that caused the event to happen. For example, a mouse event will contain information about the position of the mouse when the event happened and a keyboard event will contain information about which key was pressed.

Let’s examine a simplified event object:

javascript
	{
	  type: 'click',
	  target: <button id="example-button">Click Me!</button>,
	}

This object tells us that the user clicked the button with the id example-button. Let’s look at an example keyboard event:

javascript
	{
	  type: 'keydown',
	  target: <input id="example-input" type="text" />,
	  key: 'G',
	}

This object tells us that the user pressed the G key while the input with the id example-input was focused. This could be useful for setting up keyboard shortcuts for a web game, for example.

Event Methods

Events also contain methods that allow us to interact with them. The most common methods are:

  • preventDefault() - Prevents the default behaviour of the event from happening
  • stopPropagation() - Stops the event from bubbling up to parent elements
  • stopImmediatePropagation() - Stops the event from bubbling up to parent elements and prevents other event listeners from being triggered
  • composedPath() - Returns an array of all the elements that the event passed through on its way to the target element

Generally speaking, we will only be using the event.preventDefault() method in this course. This method is useful for preventing the default behaviour of an element from happening. For example, we can use this method to prevent a form from refreshing the page when we click submit.

We will discuss the remaining methods in more detail in the next section.

Event Bubbling

When an event occurs in the DOM, it is created by the element that the event happened to. For example, clicking on a button will create a click event that is created by the button. We call this element the target of the event. However, the event does not stop there. The event will bubble up to the parent element of the target element. This means that the parent element will also receive the event. This process will continue until the event reaches the document element. This is called event bubbling.

Event Bubbling allows us to listen for events “higher up” in the document instead of by listening to lots of specific elements individually. In some rare cases, we may want to stop an event from bubbling up to parent elements. We can do this using the event.stopPropagation() method. In most cases, we will not need to use this method.

One way to think about event bubbling is that it is an inversion of cascading in CSS, where style information “flows” downwards through the document. In event bubbling, the event “bubbles” upwards through the document in the reverse direction.

Event Listeners

In order to respond to events, we need to listen for them. We can do this by adding an event listener to an element. An event listener is a function that is called whenever a specific event happens to an element. We can add an event listener to an element using the addEventListener() method. This method takes two arguments, the type of event to listen for and the callback function to call when the event happens.

javascript
	const button = document.querySelector('#example-button');
	
	button.addEventListener('click', () => {
	  alert('The button was clicked!');
	});

It helps to make JavaScript files easier to read if we we name each callback function (handler) using a specific pattern:

javascript
	const button = document.querySelector('#example-button');
	
	function onButtonClick(event) {
	  alert('The button was clicked!');
	}
	
	button.addEventListener('click', onButtonClick);

Since a page may have many distinct buttons with different responsibilities, we can be more specific about the name of the callback function:

javascript
	function onExampleButtonClick(event) {
	  alert('The button was clicked!');
	}
	
	function onExitButtonClick(event) {
	  alert('See you next time!');
	}
	
	function onSaveButtonClick(event) {
	  alert('Your changes have been saved!');
	}

Each event handler function has a single argument, the event object. This object contains information about the event that happened. We can use this information to decide what to do when the event happens. In some cases we may not need to use this information, but it is always passed to the event handler function. This event object also contains the methods we discussed above, let’s take a look at how we can use them:

javascript
	const link = document.querySelector('#example-link');
	
	function onExampleLinkClick(event) {
	  event.preventDefault();
	  event.stopPropagation();
	  alert('The link was clicked!');
	}
	
	link.addEventListener('click', onExampleLinkClick);

In this example, we are listening for the click event on the link with the id example-link. When the event happens, we call the onExampleLinkClick() function. The first line calls the event.preventDefault() method. This method prevents the default (normal) behaviour of the link from happening, which is to navigate to the URL specified in the href attribute. The second line calls the event.stopPropagation() method. This method prevents the event from bubbling up to parent elements. This means that the event will not reach the document element.

We can also use the event handler function to update the original element that the event happened to. For example, we could change the text of a button when it is clicked:

javascript
	const button = document.querySelector('#example-button');
	
	function onButtonClick(event) {
	  event.target.textContent = 'Clicked!';
	}
	
	button.addEventListener('click', onButtonClick);

In this case, event.target and button are the same element, but this may not always be the case. Consider this HTML with the listener code above:

html
	<button id="example-button">
	  [Button Text]
	  <span>[Span Text]</span>
	</button>

If you click on Span Text, the span will be updated only. If you click on Button Text, the button will be updated and this will destroy the span inside. With complicated HTML arrangements this can become an issue quite quickly, and at times we need to validate events to make sure they happen in the right place:

javascript
	function onButtonClick(event) {
	  if (event.target.tagname === "SPAN") {
	    event.target.textContent = 'Clicked!';
	  }
	}

This updated listener will only update an element if the user clicks a span. We can discriminate based on any of the properties of an HTML Element, such as ID, class, or even the text content.

Removing Event Listeners

When we no longer need an event listener, we can remove it using the removeEventListener() method. It’s important to remove event listeners when we no longer need them, otherwise they will continue to run in the background and use up memory. This can cause performance issues in large applications.

The removeEventListener() method takes two arguments, the type of event to remove and the callback function to remove. The callback function must be the same function that was passed to the addEventListener() method. We cannot remove an event listener using an anonymous function.

javascript
	const button = document.querySelector('#example-button');
	
	function onButtonClick(event) {
	  alert('The button was clicked!');
	}
	
	button.addEventListener('click', onButtonClick);
	
	// ... later in the code
	
	button.removeEventListener('click', onButtonClick);

In this example, we are removing the onButtonClick() event listener from the button with the id example-button. This means that the onButtonClick() function will no longer be called when the button is clicked. Let’s take a look at a more realistic example, a simple to-do application where items can be added and removed from a list:

html
	<ul id="todo"></ul>
	<form name="todo">
	  <input type="text" id="new" />
	  <button type="submit">Add</button>
	</form>
javascript
	const todo = document.todo;
	const todoForm = document.forms.todo;
	
	function addTodoItem(text) {
	  const li = document.createElement('li');
	  li.textContent = text;
	  li.addEventListener('click', onTodoCompleted);
	  todo.appendChild(li);
	}
	
	function onTodoCompleted(event) {
	  alert("Well done!");
	  event.target.removeEventListener('click', onTodoCompleted);
	  event.target.textContent += ' (Completed)';
	}
	
	function onNewTodo(event) {
	  event.preventDefault();
	  const text = event.target.new.value;
	  addTodoItem(text);
	  todoForm.new.value = '';
	}
	
	todoForm.addEventListener('submit', onNewTodo);

In this simple application, each item is marked as completed after it is clicked - and the event listener is removed at that point. This means that any additional clicks will not trigger the same event listener. This is not the only way to ensure that an event only runs once, however.

Fire Once

In some cases we specifically want an event listener to fire once, and once only. For example, if a user is visiting a web-based video game for the first time, we could use one-off event listeners to show a tutorial screen each time the user enters a new menu. While we could achieve this by removing the event listener the first time that it is fired, there is a more efficient way to achieve this using the once option:

javascript
	const button = document.querySelector('#example-button');
	
	function onButtonClick(event) {
	  alert('The button was clicked!');
	}
	
	button.addEventListener('click', onButtonClick, { once: true });

In this example, we are adding an event listener to the button with the id example-button. This event listener will only fire once, the first time that the button is clicked. Let’s take a look at the tutorial example:

javascript
	const characterMenu = document.querySelector('#character-menu');
	const inventoryMenu = document.querySelector('#inventory-menu');
	const tutorial = document.querySelector('#tutorial');
	
	function onCharacterMenuClick(event) {
	  tutorial.textContent = `Character Menu tutorial content goes here...`;
	}
	
	function onInventoryMenuClick(event) {
	  tutorial.textContent = `Inventory Menu tutorial content goes here...`;
	}
	
	characterMenu.addEventListener('click', onCharacterMenuClick, { once: true });
	inventoryMenu.addEventListener('click', onInventoryMenuClick, { once: true });

In this case, we do not need to manually remove the event listeners. The { once: true } option will automatically remove the event listener after it has been fired once.

Event Types

Events in JavaScript are defined by their type value. Each type represents a different situation that can happen within the browser. There are so many different types of events that we cannot list them all here. However, we can group the most common into categories based on their purpose.

Mouse Events

Mouse events are triggered by the user interacting with the mouse. These events are triggered when the user clicks, double clicks, moves the mouse, or scrolls the mouse wheel. The most common mouse events are:

Click Events

  • click - The user has clicked the mouse button
  • dblclick - The user has double clicked the mouse button

Scroll Events

  • scroll - The user has scrolled the page
  • wheel - The user has scrolled the mouse wheel

Move Events

  • mousemove - The user has moved the mouse
  • mouseover - The user has moved the mouse over an element
  • mouseout - The user has moved the mouse out of an element
  • mouseenter - The user has moved the mouse over an element
  • mouseleave - The user has moved the mouse out of an element
  • mousedown - The user has pressed the mouse button
  • mouseup - The user has released the mouse button

Drag Events

  • drag - The user is dragging an element
  • dragstart - The user has started dragging an element
  • dragend - The user has finished dragging an element
  • dragenter - The user has dragged an element into another element
  • dragleave - The user has dragged an element out of another element
  • dragover - The user is dragging an element over another element
  • drop - The user has dropped an element into another element
  • dragexit - The user has dragged an element out of another element
  • dragcancel - The user has cancelled a drag event

Mouse events contain additional information about the mouse position when the event happened. Let’s take a look at a more complex example:

javascript
	{
	  type: 'click',
	  target: <button id="example-button">Click Me!</button>,
	  clientX: 100,
	  clientY: 200,
	}

This object tells us that the user clicked the button with the id example-button while the mouse was at position 100 across, 200 down on the screen. We could use this information to create a custom tooltip that appears at the mouse position when the user clicks a button, for example.

Touch Events

Touch events are triggered by the user interacting with a touch screen. These events are triggered when the user taps, double taps, or swipes the screen. The most common touch events are:

  • touchstart - The user has touched the screen
  • touchend - The user has stopped touching the screen
  • touchmove - The user has moved their finger across the screen
  • touchcancel - The user has cancelled a touch event
  • touchenter - The user has touched the screen
  • touchleave - The user has stopped touching the screen

These events are highly dependent on the device that the user is using. Device manufacturers may implement touch events differently, so it is important to test your application on a variety of devices. It’s important to refer to caniuse to check for compatibility with older devices.

Keyboard Events

These events are triggered by the user interacting with the keyboard. These events are triggered when the user presses or releases a key. The most common keyboard events are:

  • keydown - The user has pressed a key
  • keyup - The user has released a key
  • keypress - The user has pressed a key

Keyboard events contain additional information about the key that was pressed. Let’s take a look at a more complex example where the user is pressing a combination of keys:

javascript
	{
	  type: 'keydown',
	  target: <input id="example-input" type="text" />,
	  key: 'G',
	  ctrlKey: true,
	  shiftKey: true,
	}

This object tells us that the user pressed the G key while the input with the id example-input was focused. It also tells us that the user was holding down the ctrl and shift keys at the same time. We could use this for binding special key combinations to save or load functions within a web form, for example.

Form Events

These events are covered in greater detail in a later lesson, but we will briefly cover them here. These events are triggered by the user interacting with a form. These events are triggered when the user submits a form, changes the value of an input, or changes the focus of an input. The most common form events are:

  • submit - The user has submitted a form
  • change - The user has finished changed the value of an input
  • input - The user has changed the value of an input
  • focus - The user has focused on an input
  • blur - The user has unfocused from an input
  • reset - The user has reset a form
  • select - The user has selected text from an input
  • invalid - The user has entered an invalid value into an input

Clipboard Events

These events are triggered by the user interacting with the clipboard. These events are triggered when the user copies or pastes text. The most common clipboard events are:

  • copy - The user has copied text
  • cut - The user has cut text
  • paste - The user has pasted text

Media Events

These events are triggered by the user interacting with media elements such as audio or video. These events are triggered when the user plays, pauses, or changes the volume of a media element. The most common media events are:

  • play - The user has started playing a media element
  • pause - The user has paused a media element
  • volumechange - The user has changed the volume of a media element
  • ended - The user has finished playing a media element

UI Events

These events are triggered by the user interacting with the browser interface. These events are triggered when the user changes the size of the window, changes the orientation of the device, or changes the visibility of the page. These are “higher level” events that may take more experience to use effectively. The most common UI events are:

  • resize - The user has resized the window
  • orientationchange - The user has changed the orientation of the device
  • visibilitychange - The user has changed the visibility of the page
  • fullscreenchange - The user has changed the fullscreen state of the page
  • load - An image, link, script or document has finished loading
  • unload - A document is about to be unloaded
  • error - An error has occurred

These events can be useful for enhancing the user experience, although it should be noted that resize is not a replacement for CSS Media Queries and should be used carefully.

Progress Events

These events are triggered by the browser when a long-running task is in progress. These events are triggered when the browser is downloading a file, uploading a file, or loading a page. These events are useful for showing a progress bar to the user. The most common progress events are:

  • loadstart - The browser has started loading a resource
  • progress - The browser is loading a resource
  • loadend - The browser has finished loading a resource
  • abort - The browser has aborted loading a resource
  • error - The browser has encountered an error while loading a resource
  • timeout - The browser has timed out while loading a resource

We will cover these events in a dedicated example later on, as this ties together with some more advanced concepts.

Summary

Although we have listed many different types of events, these are only the most common from each category. JavaScript contains hundreds of events, some for very specific situations. Despite the variation in event types, all events follow a common format that makes it deceptively easy to get started working with events. Adapting from a click to a submit event is not a considerable leap, and the same is true for most events.

The key to events is remembering that these happen over time, instead of instantly like most other actions in JavaScript so far. Considering timing in web applications is one of the more difficult aspects of web development, and events are a key part of this.

As a web developer you are not expected to remember every single event type, but you should be familiar with the most common and useful types. In situations where you are not sure which event to use, you can refer to the MDN Event Reference for more information.