Three Ways to Handle Events in JavaScript

JavaScript is a versatile and powerful programming language that is widely used for creating dynamic and interactive web pages and applications. One essential aspect of building these  applications is handling events. Events are actions that take place in the browser, such as mouse clicks, keyboard inputs, or the loading of a webpage. By handling events, developers can respond to these events and execute specific functions accordingly.

There are three ways developers can listen for and handle events, and it can be daunting to know which approach to use for different circumstances. In this tutorial, we will explore those three approaches: attribute handlers, property handlers, and event listeners. Each approach has its own advantages and disadvantages, and understanding them will help you choose the right method for your projects.

Prerequisites

As previously mentioned, and event is an action that takes place in the browser. When an event occurs, the browser creates a special object that contains important information about the event. Some of the information is dependent upon the type of event. For example:

  • The object for a mouse-related event contains the coordinates of the mouse when the event occurred.
  • A keyboard-related event results in an event object that contains which key(s) were pressed.
  • Scrolling the page results in an event that has information on where the page scrolled to.

But in almost every case, the Event object contains information about the element that received the event. That element is called the event target, and we access it using the Event object’s target property.

We could spend an entire tutorial on the Event object alone, but this will suffice for now.

Using Attributes

In the early versions of web browsers, attributes were the primary way developers handled events. While they fell out of favor in the early 2000s, the concept is commonly used in popular frameworks, such as React, Vue, and Angular.

An event handler attribute is special compared to other HTML attributes. It begins with the text on and includes the name of the event. For example, the attribute for handling the click event is onclick. The value assigned to the attribute must be a JavaScript expression. Any valid expression will work, but developers typically call a function to handle the event. 

The syntax is straightforward, as shown here:

1
<button onclick="showMessage()">Click Me</button>

In this example, the onclick attribute is assigned a JavaScript expression that calls the showMessage() function. When the user clicks or taps the button, the click event fires and browser executes showMessage(). The function must be defined and in scope in order for it to execute. Otherwise, an error occurs. Let’s define that function now.

1
<script>
2
function showMessage() {
3
  alert('Button clicked!');
4
}
5
</script>

This HTML is a <script/> element that defines the showMessage() function. Therefore, when users click on the button, the browser executes the showMessage() function and the text “Button clicked!” appears in an alert window. 

Attribute handlers are straightforward to use and work across all browsers. They are also quite flexible as they allow you to use any valid JavaScript expression. For example, the previous code can be simplified to the following, therefore eliminating the need to define the showMessage() function:

1
<button onclick="alert('Button clicked!')">Click Me</button>

In this example, the onclick attribute is set to directly call the built-in alert() function. It is arguably more difficult to read and understand, but it is perfectly valid. You can also use special keywords, such as event, to gain access to the Event object that the browser created. For example:

1
<!-- Pass event object to showMessage() -->
2
<button onclick="showMessage(event)">Click Me</button>
3

4
<script>
5
function showMessage(e) {
6
    const target = e.target;
7
    
8
    alert(`You clicked the ${target.tagName} element.`);
9
}
10
</script>

This example changes the onclick attribute to pass the browser-created event object to showMessage().  The showMessage() function now uses the Event object, noted as the e parameter, to retrieve the event target and display its tag name in the alert box.

Despite their ease-of-use, there are good reasons not to use attribute handlers in your web pages. Chief among them is the idea of Separation of Concerns. Mixing HTML and JavaScript can lead to code that is difficult to maintain and understand. Keeping your HTML and JavaScript separated helps improve code organization and readability.

Attribute handlers are also limited in terms of functionality compared to other approaches. So, it is advised that you use property handlers or event listeners.

Using Property Handlers

Property handlers, also known as inline event handlers, are another way of attaching events to HTML elements. While similar to attribute handlers, they involve direct manipulation of the DOM element’s properties using JavaScript. Consider the following example:

1
<button id="messageButton">Click Me</button>
2

3
<script>
4
const buttonElement = document.getElementById('messageButton');
5

6
buttonElement.onclick = function(e) {
7
    const target = e.target;
8
    
9
    alert(`You clicked the ${target.tagName} element.`);
10
};
11
</script>

In this example, we create a <button/> element with an id of messageButton. In JavaScript, we retrieve the button element using document.getElementById() and assign it to the buttonElement variable. We then assign a function to its onclick property, which will execute when the user clicks or taps the button.

There are some important points you need to be aware of when using property handlers. First, property handlers are functions. You can assign a non-function to a property handler, but you will encounter an error when the event occurs. Second, the browser automatically passes the Event object to your function. So, be sure to define a parameter if you want to access the Event object.

Property handlers keep the event handling logic within your JavaScript, providing better separation of concerns than attribute handlers. Since property handlers are set using JavaScript code, you can dynamically change or remove event handlers during runtime.

There are, however, some important disadvantages to using property handlers. First, like attribute handlers, property handlers are limited in terms of functionality and flexibility when compared to modern event listeners.

Second, you can potentially overwrite a property handler. A property handler can contain only one function at a time. Assigning event handlers directly overwrites any existing handler. Therefore, if you assign a new function to a given property handler, the previous function is lost. There are ways to work around this issue, but you might as well use event listeners.

Listening for Events

Event listeners are the modern and preferred method of handling events in JavaScript. They provide a more flexible and powerful way to attach event handlers to elements.

Like property handlers, you attach event listeners to an object programmatically. For example:

1
<button id="messageButton">Click Me</button>
2

3
<script>
4
const buttonElement = document.getElementById('messageButton');
5

6
buttonElement.addEventListener('click', function(e) {
7
    const target = e.target;
8
    
9
    alert(`You clicked the ${target.tagName} element.`);
10
});
11
</script>

Here, we use addEventListener() to attach an event listener to the button element. The first argument is the event name (in this case, ‘click‘ — notice that is lacks the ‘on‘ prefix), and the second argument is the function that will execute when the event occurs.

Event listeners are superior to attribute and property handlers for several key reasons. One of the significant advantages of event listeners is that you can attach multiple event listeners to the same element for the same event. This is not possible with attribute handlers or property handlers. For example, you can have two separate functions handling the click event of a button using event listeners:

1
buttonElement.addEventListener('click', function() {
2
  alert('Button clicked - Function 1');
3
});
4

5
buttonElement.addEventListener('click', function() {
6
  alert('Button clicked - Function 2');
7
});

This code attaches two click event listeners to the same element. When the user clicks the button, both functions executes sequentially. This modular approach to event handling allows for better organization and maintenance of your code.

Event listeners also provide control over event propagation, which is the process of an event “bubbling up” from the target element to its ancestors or “capturing down” from ancestors to the target element. Event propagation is beyond the scope of this tutorial, but using event listeners is the only way you can control what kind of propagation to use.

Another significant advantage of event listeners is dynamic event handling. With event listeners, you can dynamically add or remove event handlers during runtime. This level of control allows you to create more interactive and adaptable user interfaces. For instance, you might want to remove an event listener temporarily to prevent the user from interacting with a particular element until a specific condition is met.

1
function showMessage() {
2
  alert('Button clicked!');
3
}
4

5
const buttonElement = document.getElementById('messageButton');
6

7
// Add the event listener initially
8
buttonElement.addEventListener('click', showMessage);
9

10
// Later in the code, remove the event listener temporarily
11
buttonElement.removeEventListener('click', showMessage);
12

13
// And later, add it back
14
buttonElement.addEventListener('click', showMessage);

This code uses the addEventListener() and removeEventListener() methods to add and remove click event listeners from an element.

Event listeners promote better separation of concerns, keeping your HTML markup free from JavaScript code, and enhancing code readability and maintainability. It also follows the principles of “unobtrusive JavaScript”, which advocates keeping JavaScript behavior unobtrusive and separate from the structure of the document.

Overall, event listeners offer a more modern and sophisticated approach to handling events in JavaScript, giving developers greater control over event handling, enhanced code organization, and the ability to create more dynamic and interactive web applications. As a result, they have become the preferred method for handling events in modern web development.

Choosing the Right Approach

Now that we have covered all three methods of setting event handlers, you might wonder which one to use in your projects. The answer depends on the complexity of your application and your personal preferences. However, here are some general guidelines to help you decide:

  • For Quick Prototyping: If you are quickly prototyping a small project or experimenting with simple interactions, attribute handlers can be handy due to their simplicity.
  • For Legacy Code: In rare cases where you encounter older code that relies on attribute or property handlers, you may need to maintain the existing approach.
  • For Modern Development: For most modern web applications, event listeners are the recommended approach. They provide better separation of concerns, more flexibility, and multiple handler support.
  • For Fine Control: If you need fine-grained control over event handling and plan to manage events dynamically (adding, removing, or changing handlers during runtime), event listeners are the best choice.

The obvious exception to these guidelines is if you are using React, Vue, Angular, or any other UI framework. These frameworks use a fundamentally different approach to developing web applications, and they promote the use of attribute event handlers. In these cases, use attribute event handlers.

Conclusion

Event handling is a fundamental aspect of building interactive web applications in JavaScript. Understanding the different methods of setting event listeners and handlers will empower you to create engaging and responsive user interfaces.