47

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?

2
  • 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... Commented Feb 17, 2014 at 0:28
  • Related: Namespaced Custom Events Trigger Commented Dec 22, 2019 at 11:34

4 Answers 4

45

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
Sign up to request clarification or add additional context in comments.

8 Comments

Beauty is in its simplicity +1
very nice! saving the reference of the callback function is a nice idea.
Yeah. I like this one. Cleanest of all
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.
|
17

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 

4 Comments

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.
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 - Yes I did. Read the code :) See demo of same code with DOM element (button)
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.

4 Comments

Makes sense. I was so focused on placing the event handler with a namespace, I didn't check the docs on removeEventListener. Thank you.
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)?
But what if I don't have a reference to the function where I want to removeEventListener? Namespacing was quite convenient in jQuery.
@vsync see my answer below.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.