Skip to main content

Events

I. Event Listeners - Handlers

Resource

Events are actions occurring on your webpage, such as mouse-clicks or key-presses. Using JavaScript, we can make our webpage listen and react to these events.

There are three primary ways to go about this:

  • Specify function attributes directly on your HTML elements.
  • Set properties in the format of on<eventType>, such as onclick or onmousedown, on the DOM nodes in your JavaScript.
  • Attach event listeners to the DOM nodes in your JavaScript. → preferred method.

1. Inline Event Listener

In this example:

  • onclick is an HTML attribute, an event listener that directly specifies a string containing the JavaScript code to execute when the button clicks. This code is typically a function call but can be any valid JavaScript expression.
  • alert('Hello World') is the event handler JavaScript code that defines the action to be taken when the button is clicked.
<!-- the HTML file -->
<button onclick="alert('Hello World')">Click Me</button>

→ This solution isn’t ideal because we’re cluttering our HTML elements with JavaScript. Also, we can only set one onClick property per DOM element, so we’re unable to run multiple separate functions in response to a click event using this method.

2. Set on<eventType> on DOM Nodes

In this example:

  • onclick is a property in JavaScript similar, but is not exactly the same as the onclick attribute in HTML.
  • It allows you to assign a function as an event handler to the click event. When the element is clicked, the function assigned to the onclick property is executed.
<!-- the HTML file -->
<button id="btn">Click Me</button>
// index.js
const button = document.querySelector("#btn");
button.onclick = () => alert("Hello World");

→ Moving the JS out of the HTML and into a JS file is better, but there’s still the problem of a DOM element only being able to have one onclick property.

3. Attach Event Listeners to DOM Nodes

addEventListener is a JavaScript method used to attach event listeners to DOM elements. It usually takes in two parameters:

  • eventType: The event type (e.g., "click", "mouseover", "keydown")
  • eventHandler: The function to be executed when the event occurs. This function is often called a "callback function" because JavaScript will "call it back" later when the event happens.
<!-- the HTML file -->
<button id="btn">Click Me Too</button>
// index.js
const button = document.querySelector("#btn");

// First event listener
button.addEventListener("click", () => {
alert("Hello World");
});

// Second event listener
button.addEventListener("click", () => {
console.log("Button clicked!");
});

→ This way allows multiple event listeners if needed, it’s more flexible and powerful, though it’s more complex to set up.

Some useful events include:

4. Using 3 Methods with Named Functions

All 3 methods shown above can be used with named functions. Using named functions can clean up the code considerably.

1. Method 1:
<!-- the HTML file -->
<!-- METHOD 1 -->
<button onclick="alertFunction()">CLICK ME!</button>
// the JavaScript file
// METHOD 1
function alertFunction() {
alert("YAY! YOU DID IT!");
}
2. Method 2 and 3:
<!-- the HTML file -->
<!-- METHODS 2 & 3 -->
<button id="btn">CLICK ME!</button>
// the JavaScript file
// METHODS 2 & 3
function alertFunction() {
alert("YAY! YOU DID IT!");
}
const btn = document.querySelector("#btn");

// METHOD 2
// assigns the function reference to the event handler
btn.onclick = alertFunction;

// METHOD 3
btn.addEventListener("click", alertFunction);

Q: Why do we use alertFunction instead of alertFunction() when adding an event listener?

A: When you set up an event listener, you're telling JavaScript: "When this event happens, run this function." You want the function to run when the event occurs, not when you're setting up the listener.

  • alertFunction is a reference to the function. It's like pointing to the function and saying "Here's the function I want to run later."
  • alertFunction() is a function call that immediately calls or invokes the function. This executes the function right away.

→ If you used alertFunction(), the function would be called immediately when setting up the event listener, not when the button is clicked. The alert would pop up right away, and addEventListener would actually receive the return value of alertFunction (which is undefined) instead of the function itself.

5. Attaching Listeners to a Group of Nodes

To add a listener to each of them, we need to iterate through the whole list, like so:

<div id="container">
<button id="1">Click Me</button>
<button id="2">Click Me</button>
<button id="3">Click Me</button>
</div>
// buttons is a node list. It looks and acts much like an array.
const buttons = document.querySelectorAll("button");

// we use the .forEach method to iterate through each button
buttons.forEach((button) => {
// and for each one we add a 'click' listener
button.addEventListener("click", () => {
alert(button.id);
});
});

II. Event Flow

Resource

<!DOCTYPE html> 
<html>
<head>
<title>JS Event Demo</title>
</head>

<body>
<div id="container">
<button id='btn'>Click Me!</button>
</div>
</body>

When you click the button, you’re clicking not only the button but also the button’s container, the div, and the whole webpage.

→ Even flow explains the order in which events are received on the page from the element where the event occurs and propagated through the DOM tree.

There are two main event models: event bubbling and event capturing.

1. Event Bubbling

In the event bubbling model, an event starts at the most specific element and then flows upward toward the least specific element (the document or window).

For example, when you click the button, the click event occurs in the following order:

  1. <button>
  2. <div> with id="container"
  3. <body>
  4. <html>
  5. document

The click event goes up the DOM tree, firing on each node along its way until it reaches the document object. Modern web browsers bubble the even up to the window object.

2. Event Capturing

In the event capturing model, an event starts at the least specific element and flows downward toward the most specific element.

For example, when you click the button, the click event occurs with the following order:

  1. document
  2. <html>
  3. <body>
  4. <div> with id="container"
  5. <button>

3. DOM Level 2 Event Flow

DOM level 2 events specify that event flow has three phases:

  • First, event capturing occurs, which provides the opportunity to intercept the event.
  • Then, the actual target receives the event.
  • Finally, event bubbling occurs, which allows a final response to the event.

4. Event Object

When the event occurs, the web browser passes an Event object to the event handler. This Event object contains information about the event, such as its type (click in this case), the target element on which the event occurred, and any additional data related to the event.

When you define an event listener using addEventListener, the browser automatically passes an Event object as the first argument to the event handler function when the event occurs.

let btn = document.querySelector('#btn');
btn.addEventListener('click', function(event) {
console.log(event.type); // output: 'click'
});

The following table shows the most commonly used properties and methods of the event object:

Note that the event object is only accessible inside the event handler. Once all the event handlers have been executed, the event object is automatically destroyed.

Property / MethodDescription
bubblestrue if the event bubbles
cancelabletrue if the default behavior of the event can be canceled
currentTargetthe current element on which the event is firing
defaultPreventedreturn true if the preventDefault() has been called.
detailmore information about the event
eventPhase1 for capturing phase, 2 for target, 3 for bubbling
preventDefault()cancel the default behavior for the event. This method is only effective if the cancelable property is true
stopPropagation()cancel any further event capturing or bubbling. This method only can be used if the bubbles property is true.
targetthe target element of the event
typethe type of event that was fired

1. preventDefault() method

To prevent the default behavior of an event, you use the preventDefault() method.

Example: When you click a link, the browser navigates you to the URL specified in the href attribute:

<a href="https://www.javascripttutorial.net/">JS Tutorial</a>

However, you can prevent this behavior by using the preventDefault() method of the event object:

let link = document.querySelector('a'); link.addEventListener('click',function(event) { 
console.log('clicked');
event.preventDefault();
});

→ Clicking the link will now only execute the code within the event handler (console.log('clicked')), and the browser won't navigate to the linked URL.

  • The preventDefault() method does not stop the event from bubbling up the DOM to parent elements → parent elements can still have their event listeners triggered for the same event.
  • An event can be canceled when its cancelable property is true.

2. stopPropagation() method

The stopPropagation() method immediately stops the flow of an event through the DOM tree. However, it does not stop the browser’s default behavior.

<a href="https://www.javascripttutorial.net/">JS Tutorial</a>
btn.addEventListener('click', function(event) { 
console.log('The button was clicked!');
event.stopPropagation();
});
document.body.addEventListener('click',function(event)
console.log('The body was clicked!'); // never gets triggered because no bubbling
});

stopPropagation() prevents the click event from bubbling to the body, but the browser's default behavior of navigating to the linked URL (defined in the href attribute) still occurs because preventDefault() is not used.

III. Page Load Events

Resource

  • [JavaScript Page Load Events]

When you open a page, the following events occur in sequence:

  • DOMContentLoaded - the browser fully loaded HTML and completed building the DOM tree. However, it hasn’t loaded external resources such as stylesheets and images. In this event, you can start selecting DOM nodes or initialize the interface.
  • load - the browser fully loaded the HTML and also external resources like images and stylesheets.

When you leave the page, the following events fire in sequence:

  • beforeunload - fires before the page and resources are unloaded. You can use this event to show a confirmation dialog if you really want to leave the page.
    • Adding a beforeunload listener with preventDefault() triggers the browser to display a confirmation dialog for the user, and their choice (OK or Cancel) determines whether navigation proceeds or preventDefault() remains in effect. → this is something the browser automatically does.

→ prevent data loss in case you’re filling out a form and accidentally click a link to navigate to another page.

  • unload - fires when the page has completely unloaded. You can use this event to send the analytic data or clean up resources.

Handling JavaScript page load events

To handle the page events, you can call the addEventListener() method on the document object.

addEventListener('DOMContentLoaded', (event) => {
console.log('The DOM is fully loaded.');
});


addEventListener('load', (event) => {
console.log('The page is fully loaded.');
});


addEventListener('beforeunload', (event) => {
// show the confirmation dialog
event.preventDefault();

// Google Chrome requires returnValue to be set.
event.returnValue = '';
});

addEventListener('unload', (event) => {
// send analytic data
});

IV. Event Delegation

Resource

<ul id="menu">
<li><a id="home">home</a></li>
<li><a id="dashboard">Dashboard</a></li>
<li><a id="report">report</a></li>
</ul>

To handle the click event of each menu item, you may add the corresponding click event handlers:

let home = document.querySelector("#home"); 
home.addEventListener("click", (event) => {
console.log("Home menu item was clicked");
})

let dashboard = document.querySelector('#dashboard');
dashboard.addEventListener('click',(event) => {
console.log('Dashboard menu item was clicked');
});

let report = document.querySelector('#report');
report.addEventListener('click',(event) => {
console.log('Report menu item was clicked');
});

Having many event handlers on a page will directly impact the performance:

  • Each event handler is a function which is also an object that takes up memory. The more objects in the memory, the slower the performance.
  • It takes time to assign all the event handlers, which causes a delay in the interactivity of the page.

To solve this issue, we can leverage event bubbling. Instead of having multiple event handlers, you can assign a single event handler to handle all the click events:

let menu = document.querySelector('#menu');

menu.addEventListener('click', (event) => {
let target = event.target;

switch(target.id) {
case 'home':
console.log('Home menu item was clicked');
break;
case 'dashboard':
console.log('Dashboard menu item was clicked');
break;
case 'report':
console.log('Report menu item was clicked');
break;
}
});
  • Instead of handling click events of the individual children, we can capture the click event at the parent element #menu.
  • In the click event listener, you can access the target property which references the element that dispatches the event. We get the id of the element that the event fired using target.id property.
  • Once we have the element’s id, we can have the code that handles the event accordingly.

Handling the too-many-event-handlers problem is called the event delegation. By doing this, you gain the following benefits:

  • Less memory usage, better performance.
  • Less time is required to set up event handlers on the page.
  • The document object is available immediately after the HTML is parsed. So as long as the element is rendered, it can start functioning correctly without delay. You don’t need to wait for the DOMContentLoaded or load events.

V. Dispatch Event

Resource JavaScript dispatchEvent

1. Event Constructor

Typically, events are generated by user actions (mouse clicks, key presses). In addition, events can be generated from code. To create a new event, we use Event constructor:

// syntax
let event = new Event(type, [,options]);

// e
let clickEvent = new Event('click');

The Event constructor takes two parameters:

  • type: a string that specifies the event type. e.g. click.
  • option:: an object with two optional properties
    • bubbles: a boolean value determining if the event bubbles or not, defaults to false.
    • cancelable: a boolean value specifying whether the event is cancelable, default to false.

2. dispatchEvent() method

After manually creating an event, you can fire (dispatch) it on a target element using the dispatchEvent() method:

element.dispatchEvent(event); 

Example: In this example, we manually create a click event and fire it on a button. Even if there’s no user click action, the dispatched clickEvent will still trigger the event handler execution as if the event were generated by user actions.

<button class="btn">Test</button>
let btn = document.querySelector(".btn"); 

btn.addEventListener("click", () => {
alert("Mouse Clicked!");
})

let clickEvent = new Event("click");
btn.dispatchEvent(clickEvent);

If the event comes from the user's actions, the event.isTrusted property is set to true. In case the event is generated by code, event.isTrusted is false. → examining event.isTrusted property can check the authenticity of the event.

Dispatching events is useful for testing simulating user interactions and testing how the code reacts to events without actual user input.

3. Custom Events

Resource JavaScript Custom Events

To create a custom event, use the CustomEvent() constructor.

// syntax
let event = new CustomEvent(eventType, options);

// example
let event = newCustomEvent("highligh", {
detail: {backgroundColor: 'yellow'}
})

The CustomEvent() has two parameters:

  • evenType: a string representing the name of the event.
  • options: an object has the detail property containing any custom information about the event.

After creating a custom event, you need to attach the event to a DOM element and trigger it by using the dispatchEvent() method.

// syntax
domElement.dispatchEvent(event);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Custom Event</title>
</head>
<body>
<div class="note">JS Custom Event</div>
<script>
function highlight(elem) {
const bgColor = 'yellow';
elem.style.backgroundColor = bgColor;

// create the event
let event = new CustomEvent('highlight', {
detail: {
backgroundColor: bgColor
}
});
// dispatch the event
elem.dispatchEvent(event);
}

// Select the div element
let div = document.querySelector('.note');

// Add border style
function addBorder(elem) {
elem.style.border = "solid 1px red";
}

// Listen to the highlight event
div.addEventListener('highlight', function (e) {
addBorder(this);

// examine the background
console.log(e.detail);
});

// highlight div element
highlight(div);
</script>
</body>
</html>