46

I'm familiar with namespaces in jQuery's event handlers. I can add an event handler in a specific namespace:

$('#id').on('click.namespace', _handlerFunction);

And then I can remove all event handlers in that namespace:

$('#id').off('.namespace');

The advantage here is that I can remove only the events in this namespace, not any user-added/additional events that should be maintained.

Does anyone have any tips on how I can not use jQuery, but achieve a similar result?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tyler Conover
  • 463
  • 1
  • 4
  • 5
  • http://danml.com/js/events.js has stuff like that: you can select with a regexp. also search bower and microjs for "event" for more stuff like that... – dandavis Feb 17 '14 at 00:28
  • Related: [Namespaced Custom Events Trigger](https://stackoverflow.com/q/4718841/104380) – vsync Dec 22 '19 at 11:34

4 Answers4

43

For anyone still looking for this, I ended up making a helper singleton which keeps track of the function references for me.

class EventHandlerClass {
  constructor() {
    this.functionMap = {};
  }

  addEventListener(event, func) {
    this.functionMap[event] = func;
    document.addEventListener(event.split('.')[0], this.functionMap[event]);
  }

  removeEventListener(event) {
    document.removeEventListener(event.split('.')[0], this.functionMap[event]);
    delete this.functionMap[event];
  }
}

export const EventHandler = new EventHandlerClass();

Then just import EventHandler and use like:

EventHandler.addEventListener('keydown.doop', () => console.log("Doop"));
EventHandler.addEventListener('keydown.wap', () => console.log("Wap"));
EventHandler.removeEventListener('keydown.doop');
// keydown.wap is still bound
Andrew
  • 1,890
  • 3
  • 16
  • 35
  • 1
    Beauty is in its simplicity +1 – Scott L Jul 06 '18 at 17:27
  • 1
    very nice! saving the reference of the callback function is a nice idea. – scipper Mar 26 '19 at 08:06
  • 1
    Yeah. I like this one. Cleanest of all – Sampgun May 21 '19 at 13:08
  • @MaxCore yes, https://developer.mozilla.org/en-US/docs/Web/API/Event/Event – ADJenks Sep 16 '19 at 23:19
  • Renamed to remove the conflict with the existing Event API – Andrew Sep 18 '19 at 20:21
  • 1
    If you add an event with the same signature without removing the prior, you would lose reference to the previous one, there would be two events on the item, but one bound reference. You need better control on the `functionMap`. – Taha Paksu Feb 27 '20 at 07:01
  • True, though I'd probably suggest adding error handling to alert you if you're trying to bind a new function over an existing event so that you can pick a more specific namespace for your event rather than trying to gracefully handle binding multiple functions to the same event, as removing an event listener from an event which has two functions bound to it is likely to cause bugs if you are not expecting it to unbind both functions. – Andrew Feb 29 '20 at 22:14
  • Downvoted. This does not show how to add an event listener to a dom element. This is a global solution for the document. – Craig Sep 26 '22 at 22:17
16

In this solution I've extended the DOM to have on and off methods with the ability to use events namespacing:

var events = {
  on(event, cb, opts){
    if( !this.namespaces ) // save the namespaces on the DOM element itself
      this.namespaces = {};

    this.namespaces[event] = cb;
    var options = opts || false;
    
    this.addEventListener(event.split('.')[0], cb, options);
    return this;
  },
  off(event) {
    this.removeEventListener(event.split('.')[0], this.namespaces[event]);
    delete this.namespaces[event];
    return this;
  }
}

// Extend the DOM with these above custom methods
window.on = Element.prototype.on = events.on;
window.off = Element.prototype.off = events.off;


window
  .on('mousedown.foo', ()=> console.log("namespaced event will be removed after 3s"))
  .on('mousedown.bar', ()=> console.log("event will NOT be removed"))
  .on('mousedown.baz', ()=> console.log("event will fire once"), {once: true});

// after 3 seconds remove the event with `foo` namespace
setTimeout(function(){
    window.off('mousedown.foo')
}, 3000)
Click anywhere 
vsync
  • 118,978
  • 58
  • 307
  • 400
  • 2
    I upvoted this, but there are caveats associated with storing js object references in the DOM, as it can lead to memory leaks if you don't know what you are doing. This could happen if you remove one of the elements from the DOM, while it still stores additional references to an event handler, for example. Just make sure you call your element's 'off' method if you intend to remove that element and all should be well. I think most(?) modern(?) garbage collectors are wise to this issue, but there are certainly some (especially older) browsers out there that aren't. – brennanyoung Jun 08 '18 at 09:10
  • 1
    Related: [addEventListener-memory-leaks](https://stackoverflow.com/q/2631265/104380) – vsync Dec 22 '19 at 11:32
  • Downvoted this, you didn't provide a way to addEventListener to a dom element. Looks like you added a global event listener to the window. This is not what the OP asked for. – Craig Sep 26 '22 at 22:16
  • 1
    @Craig - Yes I did. Read the code :) [See demo](https://jsbin.com/ritoyir/edit?html,css,js,console,output) of same code with DOM element (button) – vsync Sep 27 '22 at 10:02
11

I think you are looking for addEventListener and removeEventListener. You can also define custom events and fire them using dispatchEvent.

However, to remove an event listener, you will need to retain a reference to the event function to remove just the function you want to remove instead of clearing the whole event.

Will P.
  • 8,437
  • 3
  • 36
  • 45
  • Makes sense. I was so focused on placing the event handler with a namespace, I didn't check the docs on removeEventListener. Thank you. – Tyler Conover Feb 17 '14 at 08:30
  • 21
    How does this help with namespacing an event? can you show an example how one could namespace a `mouseup` event on the `window` and then remove just that one (assuming there were others non-namespaced as well)? – vsync Jul 26 '16 at 08:23
  • 1
    But what if I don't have a reference to the function where I want to removeEventListener? Namespacing was quite convenient in jQuery. – Parth Jan 12 '17 at 13:04
  • @vsync see my answer below. – Andrew Jun 08 '17 at 03:32
2

Approaches to create event group(namespace):

  1. Pass AbortController.signal to the 'signal' property in the 'options' parameter of the addEventListener API.
  2. Atrach all events of the group to separate HTMLElement.
  3. Cache event type, listener and useCapture for each subscription.

Remove all events within a group:

  1. Call AbortController.abort(): all events, that are configured with this AbortController instance 'signal' will be removed.
  2. Remove the HTMLElement: This will destroy all event listeners, associated with the HTMLElement.
  3. Use removeEventListener by passing the type, listener and useCapture configuration of the event.

Note: The 2nd approach requires events to be dispatched for every group EventTarget(HTMLElement). The other approaches allow single EventTarget to be used for event subscription and dispatch.

appostolov
  • 21
  • 1