jayarjo
jayarjo

Reputation: 16716

JavaScript: remove an event listener from within that listener?

I always wondered how clean is such approach - to remove an event listener from within that very listener.

UPDATE:

Internally I keep a hash of objects and listeners, so I potentially can remove event listener from any place. I'm just concerned of removing it from within itself. Will such action do a job actually?

UPDATE

I'm asking about addEventListener, removeEventListener stuff.

Upvotes: 34

Views: 30534

Answers (8)

Bryce Hawk
Bryce Hawk

Reputation: 1

The simplest answer is addEventListener('',function,{once:true}), but its functionality is a bit limited. I found a more customizable way so I thought I'd share.

The addEventListener() docs state that event options object (That allows for the {once:true} functionality) can also take an abortSignal. This abort signal can be triggered from anywhere that has a reference to the controller object!

Here is the functionality I needed boiled down into a simple example:

function eventCreator(){ 
  let moveController = new AbortController()

  const onEscapeKey = (e:KeyboardEvent) => {
    if(e.key !== 'Escape') return
    /* ... do something useful */
    moveController.abort()
  }
  const onMouseDown = (e:MouseEvent) => {
    if(e.button !== 0) return
    /* ... do something else useful */
    moveController.abort()
  }
  const onMouseMove = () => {
    /* Do Something completely irrelevant to the other functions */
  }
  
  document.addEventListener('mouseMove', onMouseMove, {signal:moveController.signal})
  document.addEventListener('mousedown', onMouseDown, {signal:moveController.signal})
  document.addEventListener('keydown', onEscapeKey, {signal:moveController.signal})

}

The main function creates (or binds) all the sub-functions it needs. When it applies the functions to their respective DOM elements it hands a newly created abortSignal over.

In this instance the mouseMove function begins getting called immediately. It keeps getting called until the user either, a) presses the escape key or b) presses the left mouse button. When either of those latter events occur all 3 event listeners are signaled to be cleaned-up.

In my case I had the mousemove listener controlling the location of an object, the escape listener deleted the object, and the click listener placed the object.

Hands down my new favorite way of cleaning up events when compared to storing function references and calling removeEventListener() once everything is done.

Upvotes: 0

markhuang1994
markhuang1994

Reputation: 71

If you want listener only trigger once, you can use this code:

element.addEventListener('eventname', function callback(){}, { once: true }); 

Or use wrapper to do the same thing:

function addOneTimeEventListener(element, event, callback) {
    const wrapper = e => {
        try {callback(e)} finally {
            element.removeEventListener(event, wrapper);
        };
    }
    element.addEventListener(event, wrapper);
}

// example
addOneTimeEventListener(document.body, 'click', e => {
  console.log('this message only show once.');
});

If you want to decide when to remove listener:

function addEventListener(element, event, callback) {
    const wrapper = e => {
        callback(e, () => element.removeEventListener(event, wrapper));
    }
    element.addEventListener(event, wrapper);
}

// example
let count = 0;
addEventListener(document.body, 'click', (e, closeListener) => {
  console.log(`click:${++count}`);
  if(count == 3) closeListener();
});

Upvotes: 3

bharal
bharal

Reputation: 16154

I just saw this because i wondered the exact same question!

arguments.callee is your friend...

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

so you'd have

blah.addEventListener('click',function(e){
    e.source.removeEventListener('click', arguments.callee);
    blee bloo bleep
});

this works in Titanium Appcelerator, so it should work in javascript too (because they are The Same Thing Kinda)

NB do NOT add () to the end of arguments.callee in this example, unless you like seeing... bah dum tish!.

In fact, if you don't want to use arguments.callee, this also might work (untested):

blah.addEventListener('click', anyThingYouWantHere = function(e){
    e.source.removeEventListener('click', anyThingYouWantHere);
    blee bloo bleep
});

Where "anythingYouWantHere" is any variable name you'd like ~ you're effectively "naming" the function as you add it.

Upvotes: 19

broc.seib
broc.seib

Reputation: 22731

You can pass the once option to have a listener act only once, then remove itself. Docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters

Example:

  element.addEventListener('eventname', (ev) => {
    console.log("event is captured only once.");
    // do more stuff...
  }, { once: true });

From the same docs link above, modern browser support is good, but is not available for Internet Explorer.

Upvotes: 73

Philipp Mochine
Philipp Mochine

Reputation: 4705

@bharal answer gave me now this solution:

//example
addBlurListener(element, field) {
    const listenToBlur = (e) => {
        e.target.removeEventListener(e.type, listenToBlur);
        //your stuff
    };
    element.addEventListener('blur', listenToBlur);
},

Upvotes: 4

JCLaHoot
JCLaHoot

Reputation: 1054

I just made a wrapper function that generates a self destructing event listener:

let addSelfDestructingEventListener = (element, eventType, callback) => {
    let handler = () => {
        callback();
        element.removeEventListener(eventType, handler);
    };
    element.addEventListener(eventType, handler);
};

So far it works great :)

Upvotes: 13

Cyranix
Cyranix

Reputation: 337

If you use jQuery, you will probably have some convenience methods exposed for interacting with event handlers -- see bind()/unbind(), delegate()/undelegate(), one(), and similar methods.

I don't have much experience with other frameworks, but I'd imagine they offer similar functionality. If you're not using a framework at all, @sdleihssirhc has an acceptable answer.

EDIT: Ah, perhaps you're looking for something more like addEventListener() and removeEventListener(). Again, a framework will offer some convenience to your interactions and save you the trouble of reinventing the wheel in some cases.

Upvotes: 0

sdleihssirhc
sdleihssirhc

Reputation: 42496

You could try something like this, depending on how it's called:

some_div.onclick = function () {
    ...
    this.onclick = null;
    // or: some_div.onclick = null;
};

Or is it event listeners you're concerned with? Because those are a little bit more complicated.

Upvotes: 1

Related Questions