Reputation: 16716
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
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
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
Reputation: 16154
I just saw this because i wondered the exact same question!
arguments.callee is your friend...
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
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
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
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
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
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