Published at
Updated at
Reading time
2min

This post is part of my Today I learned series in which I share all my learnings regarding web development.

To build interactive web interfaces, you have to use DOM (Document Object Model) events. How does that usually work?

You define the event type you're interested in, pair it with a callback function and you are ready to react to clicks, keypresses, scrolls and many other events.

For example, to react to a button click, the following code can be used:

document.querySelector('button')
  .addEventListener('click', () => {
    console.log('element clicked');
  });

The code queries the DOM, grabs a specific element and adds a click event listener to it using addEventListener.

According to MDN, target.addEventListener defines the following parameters:

target.addEventListener(type, listener [, options]);
target.addEventListener(type, listener [, useCapture]);
target.addEventListener(type, listener [, useCapture, wantsUntrusted  ]); // Gecko/Mozilla only

addEventListener accepts the event type, a listener callback function and an options or useCapture parameter.

(To learn more about possible options or useCapture head over to the MDN addEventListener documentation.)

What if I told you, that the listener parameter can be a function but that it can be an object, too?

addEventListener and the EventListener interface

Section titled `addEventListener` and the `EventListener` interface

It turns out that MDN documents listener as the following:

[listener] must be an object implementing the EventListener interface, or a JavaScript function.

The early DOM events specification (we're talking pre-HTML5 here) defined an EventListener interface. Objects implementing the interface (they had to define a handleEvent method) where valid to be used with addEventListener.

// a class implementing
// the `EventListener` interface
class EventHandler {
  constructor() {
    this.eventCount = 0;
  }

  handleEvent() {
    this.eventCount++;
    console.log(`Event triggered ${this.eventCount} time(s)`);
  }
}

document.querySelector('button')
  .addEventListener('click', new EventHandler());

The code above defines a JavaScript class EventHandler. Initialized event handler objects can be passed to addEventListener to react to specific events. The event handlers then keep track of the number of times a specific event occurred (check it on CodePen). All information is stored in the objects itself, and the code works without any outer scope variables. I like this pattern and I can see it come in handy when dealing with sequential events.

According to MDN, the EventListener interface is supported by all major browsers and you can safely pass objects that implement it to addEventListener.

When would you pass EventListener objects to addEventListener? I'd love to learn about more examples!

Edit: Someone shared the following snippet on Reddit.

class MyComponent {
  constructor (el) {
    this.el = el
    this.el.addEventListener('click', this)
  }
  handleEvent (event) {
    console.log('my component element was clicked')
  }
  destroy () {
    this.el.removeEventListener('click', this)
  }
}

const component = new MyComponent(
  document.querySelector('button')
);

The MyComponent class accepts a DOM element and attaches/detaches event listeners to it automatically. It also implements the EventListener interface which means that you can pass this to addEventListener. I have to say, I like that pattern!

Related Topics

Related Articles