Go to homepage

Projects /

How to handle events in Javascript and tips on keeping them organised

An article on using the addEventListener and removeEventListener methods and managing events with the handleEvent function.

How to handle events in Javascript and tips on keeping them organised
By CodyHouse
404 HTML, CSS, JS components. Download →

In this tutorial, we will show how to add and remove event listeners in JavaScript using the two methods addEventListener and removeEventListeners. We'll also share some tips on organizing listeners using the handleEvent function, with practical examples to illustrate applications of this method in real-world scenarios.

Handling events in JavaScript - addEventListener and removeEventListener methods

The addEventListener method lets you specify a function that should be called each time a specific event is delivered to the target element:

var element = document.getElementsByClassName('my-class');
if(element.length > 0) {
  element[0].addEventListener('click', function(event) {
    // function you want to trigger on click
  });
}

The function accepts three arguments:

  1. the event that should trigger a function; in the example above, the 'click' event;
  2. the function to be executed when the event is triggered;
  3. options to customize the listener (optional).

These are the possible options that you can pass as the third parameter:

1 - addEventListener - once option

Boolean value. If this is set to true, the listener function will be invoked only once; it will then be automatically removed from the target. Default value is false.

element[0].addEventListener('click', function(event) {
  // this function will be called only once; the listener will then be removed automatically
}, {once: true});

2 - addEventListener - capture option

Boolean value. If set to true, this type of event will be dispatched to the this listener before any other target elements beneath it in the DOM tree. Default value is false.

For example, consider the following code:

var parent = document.getElementById('parent'),
  child = document.getElementById('child');

parent.addEventListener('click', function(){
  console.log('click on parent element');
});

child.addEventListener('click', function(){
  console.log('click on child element');
});

where the child element is a child of the parent element. By defult, if you click the child element, the first message to be printed in the console will be 'click on child element' (event on child is triggered before parent).

If we now modify the parent listener and set the capture option to true:

parent.addEventListener('click', function(){
  console.log('click on parent element');
}, {capture: true});

the first event to be triggered will be the one on the parent (first message printed in the console will be 'click on parent element').

3 - addEventListener - passive option

Boolean value. If set to true, it specifies that the listener function will never call the preventDefault method.

element[0].addEventListener('click', function(event) {
  // this function cannot use the preventDefault method
}, {passive: true});

4 - addEventListener - signal option

This is used to remove event listeners. You can create an AbortSignal object; calling the abort method on that object will result in the removal of the listener.

Consider the following example:

var parent = document.getElementById('parent');
// create an AbortSignal object
const abortSignal = new AbortController();

parent.addEventListener('click', function(event) {
  if(event.target.closest('#child')) {
    console.log('click');
  } else {
    abortSignal.abort(); // this will remove the listener
  }
}, {signal: abortSignal.signal});

When clicking on the parent element, the listener function checks if the click was on its child element #child. If it was, then a message is printed to the console, otherwise, the listener is removed from the parent.


As seen above, the addEventListener method comes with two options to automatically remove event listeners: once and signal.
For scenarios not addressed by these options, you can use the removeEventListener function:

function clickHandler(event) {
  // function you want to trigger on click
};

// add event listener 
element[0].addEventListener('click', clickHandler);

// remove event listener
element[0].removeEventListener('click', clickHandler);

Note how this time, we are not using, as a listener, an anonymous function (function(event) {}), but a named function (clickHandler).
This is required when using the removeEventListener method; otherwise, the event won't be successfully removed.

Keep events organized with the handleEvent method

In the first part of this article, we said that the second parameter of the addEventListener method should be a JavaScript function (either an anonymous or named function). This parameter could also be an object that should have a handleEvent method; this method automatically takes care of handling all the event listeners.

Let's consider this example:

var Modal = function(element) {
  this.element = element;
  // other Modal properties defined here
  // ...

  // use the initEvents method to add event listeners
  this.initEvents();
};

Modal.prototype.initEvents = function() {
  // we are passing, as the listener, the object 'this' (Modal object), so events will be automatically handled by its handleEvent method
  this.element.addEventListener('click', this);
  this.element.addEventListener('mouseenter', this);
};

Modal.prototype.handleEvent = function(event) {
  switch(event.type) {
    case 'click': {
      // code to be executed on click
      console.log('element clicked');
      break;
    }
    case 'mouseenter': {
      //...
    }
    // all other events here
  }
};

In the above example, we define a Modal object, and use the initEvents method to add all the listeners.
For all the events, we use the same listener, the this object (which is the Modal object itself).
Since we are passing an object as the listener, the handleEvent method of that object will automatically take care of handling the events. In the example above, we use a switch statement to check the event type, and execute a function accordingly.

This way, we can keep all our listeners organized in one place; this not only improves the maintainability of our code (we know where to look if we need to modify a listener function), but it also avoids adding listeners multiple times (we may forget we have already used that event somewhere in our code).

Additionally, this technique makes it super easy to remove events: we do not have to remember the named function to use for each event; we can simply pass the same object as the listener:

Modal.prototype.removeEvents = function() {
  this.element.removeEventListener('click', this);
  this.element.removeEventListener('mouseenter', this);
}; 

Real-life examples

Let's see these methods in action in some real-life examples.

Dialog component

Take a look at this Dialog component

It is used to open a dialog element when clicking on a trigger button. When the dialog is open, we want to add two event listeners: click and keydown.

The click event is used to close the dialog with the 'Cancel' button element.

The keydown event is used to trap the focus while the dialog is open: if user presses the Tab key, we want the focus to move only among the focusable children of the dialog; this way we are sure the focus is not moved to an element not visible (e.g., under the dialog), improving accessibility for keyboard users.

We can define a Dialog object and an initDialogEvents function to handle the two events:

var Dialog = function(element) {
  this.element = element;
  // all Dialog properties here
  //...

  this.binding = false; // we will use this to store a reference to the event listener
  initDialog(this);
};

function initDialog(dialog) {
  // dialog.trigger is the <button> element used to open the dialog 
  dialog.trigger.addEventListener('click', function(event) {
    // when opening the dialog -> add event listeners
    initDialogEvents(dialog);
  });
};

function initDialogEvents(dialog) {
  // using dialog.binding to save a refernce to the event listener
  dialog.binding = handleEvent.bind(dialog); 
  dialog.element.addEventListener('keydown', dialog.binding);
  dialog.element.addEventListener('click', dialog.binding);
};

function handleEvent(event) {
  // handle events
  switch(event.type) {
    case 'click': {
      initClick(this, event); // 'this' -> 'dialog' element
      break;
    }
    case 'keydown': {
      initKeyDown(this, event); // 'this' -> 'dialog' element
      break;
    }
  }
};

Here's what we did:

  • We created a Dialog object;
  • When opening the dialog (click on the dialog.trigger element), we use the initDialogEvents function to listen to two events (click and keydown);
  • We use the handleEvent function to handle both events.

Two additional notes about the initDialogEvents function:

  • we use the dialog.binding property to store a reference to the event listeners; this way, we can remove the listeners using the same property:
function cancelDialogEvents(dialog) {
  //remove event listeners
  dialog.element.removeEventListener('keydown', dialog.binding);
  dialog.element.removeEventListener('click', dialog.binding);
};
  • as the listener, we use the handleEvent function, but we also bind the dialog object:
dialog.binding = handleEvent.bind(dialog);

This way, inside the handleEvent function, we can access the Dialog object using the this keyword:

function handleEvent(event) {
  // handle events
  switch(event.type) {
    case 'click': {
      initClick(this, event); // 'this' -> Dialog object
      break;
    }
    // ...
  }
};

Image magnifier component

Take a look at this Image magnifier component.

When the user hovers over the image, the plugin shows a magnified version of that image and modifies its position based on the cursor position.

We need to add two event listeners (mousemove and mouseleave) when the mouse enters the image and remove them when it leaves.

As we did for the dialog, we can define an ImageMagnifier object, then use two methods (initImageMove/cancelImageMove) to add and remove event listeners, and the handleEvent function to handle them:

var ImageMagnifier = function(element) {
  this.element = element;
  // other ImageMagnifier properties here
  // ...
  this.binding = false; // we will use this to store a reference to the event listeners
  initImageMagnifier(this); 
};

function initImageMagnifier(imgMag) {
  // listen to 'mouseenter' event 
  imgMag.binding = handleEvent.bind(imgMag);
  imgMag.element.addEventListener('mouseenter', imgMag.binding);
};

function initImageMove(imgMag) { 
  // listen to 'mousemove' and 'mouseleave' events
  imgMag.element.addEventListener('mousemove', imgMag.binding);
  imgMag.element.addEventListener('mouseleave', imgMag.binding);
};

function cancelImageMove(imgMag) {
  // remove 'mousemove' and 'mouseleave' events
  imgMag.element.removeEventListener('mousemove', imgMag.binding);
  imgMag.element.removeEventListener('mouseleave', imgMag.binding);
};

function handleEvent(event) {
  switch(event.type) {
    case 'mouseenter':
      // on 'mouseenter', use the initImageMove function to add 'mousemove' and 'mouseleave' event listeners
      initImageMove(this); 
      break;
    case 'mousemove':
      // on 'mousemove', magnify and update the position of the image
      move(this, event); 
      break;
    case 'mouseleave':
      // on 'mouseleave', remove the 'mousemove' and 'mouseleave' listeners
      cancelImageMove(this);
      break;
  }
};

Here's what we did:

  • We created an ImageMagnifier object;
  • We used the initImageMagnifier function to detect the mouseenter event;
  • Once the mouse enters the image, we use the initImageMove function to add listeners for the mousemove and mouseleave events;
  • We remove the mousemove and mouseleave listeners when leaving the image, using the cancelImageMove function;
  • We use the handleEvent function to handle all listeners.

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.