Related
I have the following code to add eventListener
area.addEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
It is working correctly as expected..Later in another function i tried to remove the event listener using the following code
area.removeEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
But the even listener is not removed..Why is it happening?Is there any problem with my removeEventListener()?
Note:Here area is something like document.getElementById('myId')
This is because that two anonymous functions are completely different functions. Your removeEventListener's argument is not a reference to the function object that was previously attached.
function foo(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
}
area.addEventListener('click',foo,true);
area.removeEventListener('click',foo,true);
I find that for the windows object, the last param "true" is required.
The remove doesn't work if there is no capture flag.
In a React function component, make sure to define the callback with the useCallback(() => {}) hook. If you fail to do this, the callback will be a different one on every re-render and the removeEventListener method will not work.
const scrollCallback = useCallback(() => { // do sth. });
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);
It looks like no one's covered the part of the DOM specification (that both browsers and Node.js implement) that now gives you a mechanism to remove your event listener without using removeEventListener.
If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed as options when setting up an event listener:
{
type (a string)
callback (an EventListener object, null by default)
capture (a boolean, false by default)
passive (a boolean, false by default)
once (a boolean, false by default)
signal (an AbortSignal object, null by default)
removed (a boolean for bookkeeping purposes, false by default)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us remove an event listener by using an AbortController instead of having to bother with keeping a reference to the exact handler function and listener options "because otherwise removeEventListener won't even work properly":
const areaListener = new AbortController();
area.addEventListener(
`click`,
({clientX: x, clientY: y}) => {
app.addSpot(x, y);
app.addFlag = 1;
},
{ signal: areaListener.signal }
);
And now, when it's time to remove that event listener, we simply run:
areaListener.abort()
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same funcation and properties as we called addEventListener: we just cancel the listener with a single, argumentless, abort call.
And of course, also note that if we want to do this "because we only want the handler to fire once", then we don't even need to do this, we can just create an event listener with { once: true } and JS will take care of the rest. No removal code required.
area.addEventListener(
`click`,
() => app.bootstrapSomething(),
{ once: true }
);
You are creating two different functions in both calls. So the second function does not relate in any way to the first one and the engine is able to remove the function. Use a common identifier for the function instead.
var handler = function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
};
area.addEventListener('click', handler,true);
later you can then remove the handler by calling
area.removeEventListener('click', handler,true);
To remove it, store the function in a variable or simply use a named function and pass that function to the removeEventListener call:
function areaClicked(event) {
app.addSpot(event.clientX, event.clientY);
app.addFlag = 1;
}
area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);
If you want to pass local variables to the function called by the event listener, you can define the function inside the function (to get the local variables) and pass the name of the function in the function itself. For example, let's start inside the function that adds the event listener with app as a local variable. You would write a function inside this function such as,
function yourFunction () {
var app;
function waitListen () {
waitExecute(app, waitListen);
}
area.addEventListener('click', waitListen, true);
}
Then you have what you need to remove it when waitExecute is called.
function waitExecute (app, waitListen) {
... // other code
area.removeEventListener('click', waitListen, true);
}
define your Event Handler first,
and then
area.addEventListener('click',handler);
area.removeEventListener('click',handler);
This is what I ended up doing but it's in a route class but should not make much difference, I wanted for the event listener not to accumulate each time afterModel() is called but also needed arguments and scope so that the model is changed each time.
export default class iFrameRoute extends Route {
afterModel(model) {
this.initFrame = function(event) {
alert("I am being called");
window.removeEventListener("message", this.route.test);
}.bind({route: this, data: model});
window.addEventListener("message", this.initFrame );
}
}
I went through this same problem recently. A reasonble solution that I found was remove attribute "onclick" on element from HTMLElement class.
Let's imagine that you already got your component from DOM - using document.getElementById or document.querySelector - you can try that code:
js
const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');
html example
<div onClick="memoryGame.flipCard(this)">
.... // children elements
</div>
I know which this solution it ins't the best, but it works!
I hope I was able to help you.
Cheers!
PS: please, give me a "useful answer"... thanks :D
while adding function store in array and removing pass by map work for me
const [functionObjects, setfunctionObjects] = useState([]);
const addListener = (beforeUnloadListener) =>{
setfunctionObjects([...nano, beforeUnloadListener]);
addEventListener("beforeunload", beforeUnloadListener, {capture: true});
};
const removeListener = (beforeUnloadListener) => {
functionObjects.map((item) => {
removeEventListener("beforeunload", item, {capture: true});});
};
In case of React we can use useRef() to store our listener function in current property. So that in case of re-render and in case of remove listener it will maintain the same reference to the function.
const handleWindowClick = useRef(() => {
console.log("window clicked");
});
// for attaching event listener
window.addEventListener("click", handleWindowClick.current);
// for detaching event listener
window.removeEventListener("click", handleWindowClick.current);
Update 2023
I was using Angular and after numerous tries using AbortController() nothing solved my problem.
Finally renderer2 to the rescue. Here's what I did
mouseMoveListener :any;
mouseUpListener :any;
this.mouseMoveListener = this._renderer.listen("document", "mousemove", (event) => {
this.onMouseMove(event);
});
this.mouseUpListener = this._renderer.listen("document", "mouseup", (event) => {
this.onMouseUp(event);
});
ngOnDestroy(){
this.mouseMoveListener();
this.mouseUpListener();
}
I have some client side logic (I have a little 3d world where objects interacts), and I would like to add some events listener like this:
window.addEventListener("myAmazingEvent", () => {doSomethingAmazing})
How can I implement this eventListener to my own class? Like:
class Person {
construcor() {
this.listener = new SomeJavascriptEventListener()
}
crouched() {
this.listener.call("onCrunch")
}
const a = new Person()
a.addEventListener("onCrunch", () => {a.startSinging()})
What javascript built in classes can provide this behaviour to me?
JavaScript's standard library doesn't have any direct event emitter support. There are lots of event emitter libraries you can find with a search, but none of them is built-in.
You can:
Use one of the pre-built, pre-tested event emitter libraries that already exist (look for "event emitter" or "publish/subscribe" or "pub/sub").
Use something built into the environment you're running your code in. Node.js has an EventEmitter class. On browsers, you could create a DOM element that you keep in your class instance (no need for it to be in the document) and use it to store event listeners, then use dispatchEvent to send events to it. (Though that's not really simpler than the next option.)
Build your own by maintaining an array or Set of event listeners for each event type that you loop through and call when you need to emit an event.
All three are viable options.
But to give you an idea, a class with very basic event handling looks like this:
class Example {
#eventHandlers = {
"some-event": new Set(),
"another-event": new Set(),
};
on(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.add(handler);
}
off(eventName, handler) {
const handlers = this.#eventHandlers[eventName];
if (!handlers) {
throw new Error(`Invalid event name "${eventName}"`);
}
handlers.delete(handler);
}
#trigger(eventName, ...eventArgs) {
const handlers = this.#eventHandlers[eventName];
// assert(!!handlers);
for (const handler of handlers) {
try {
handler(...eventArgs);
} catch { // (Add (e) if your environment doesn't support optional catch bindings)
}
}
}
doSomething() {
// In this example, "doing something" triggers the `some-event` event
this.#trigger("some-event", "something happened");
}
}
const e = new Example();
e.on("some-event", (msg) => console.log(`Handler one: received "${msg}"`));
e.on("some-event", (msg) => console.log(`Handler two: received "${msg}"`));
e.doSomething();
That uses relatively modern things (private fields, optional catch bindings), but you can adapt it as needed.
There are a dozen or more ways to skin this cat. Cross-talk between nandlers, cancelling events, passing in some kind of Event object, etc., etc., etc. But the fundamentals are:
Registering handlers (on in the example)
Unregistering handlers (off in the example)
Triggering events (#trigger in the example)
I have the following code to add eventListener
area.addEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
It is working correctly as expected..Later in another function i tried to remove the event listener using the following code
area.removeEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
But the even listener is not removed..Why is it happening?Is there any problem with my removeEventListener()?
Note:Here area is something like document.getElementById('myId')
This is because that two anonymous functions are completely different functions. Your removeEventListener's argument is not a reference to the function object that was previously attached.
function foo(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
}
area.addEventListener('click',foo,true);
area.removeEventListener('click',foo,true);
I find that for the windows object, the last param "true" is required.
The remove doesn't work if there is no capture flag.
In a React function component, make sure to define the callback with the useCallback(() => {}) hook. If you fail to do this, the callback will be a different one on every re-render and the removeEventListener method will not work.
const scrollCallback = useCallback(() => { // do sth. });
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);
It looks like no one's covered the part of the DOM specification (that both browsers and Node.js implement) that now gives you a mechanism to remove your event listener without using removeEventListener.
If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed as options when setting up an event listener:
{
type (a string)
callback (an EventListener object, null by default)
capture (a boolean, false by default)
passive (a boolean, false by default)
once (a boolean, false by default)
signal (an AbortSignal object, null by default)
removed (a boolean for bookkeeping purposes, false by default)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us remove an event listener by using an AbortController instead of having to bother with keeping a reference to the exact handler function and listener options "because otherwise removeEventListener won't even work properly":
const areaListener = new AbortController();
area.addEventListener(
`click`,
({clientX: x, clientY: y}) => {
app.addSpot(x, y);
app.addFlag = 1;
},
{ signal: areaListener.signal }
);
And now, when it's time to remove that event listener, we simply run:
areaListener.abort()
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same funcation and properties as we called addEventListener: we just cancel the listener with a single, argumentless, abort call.
And of course, also note that if we want to do this "because we only want the handler to fire once", then we don't even need to do this, we can just create an event listener with { once: true } and JS will take care of the rest. No removal code required.
area.addEventListener(
`click`,
() => app.bootstrapSomething(),
{ once: true }
);
You are creating two different functions in both calls. So the second function does not relate in any way to the first one and the engine is able to remove the function. Use a common identifier for the function instead.
var handler = function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
};
area.addEventListener('click', handler,true);
later you can then remove the handler by calling
area.removeEventListener('click', handler,true);
To remove it, store the function in a variable or simply use a named function and pass that function to the removeEventListener call:
function areaClicked(event) {
app.addSpot(event.clientX, event.clientY);
app.addFlag = 1;
}
area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);
If you want to pass local variables to the function called by the event listener, you can define the function inside the function (to get the local variables) and pass the name of the function in the function itself. For example, let's start inside the function that adds the event listener with app as a local variable. You would write a function inside this function such as,
function yourFunction () {
var app;
function waitListen () {
waitExecute(app, waitListen);
}
area.addEventListener('click', waitListen, true);
}
Then you have what you need to remove it when waitExecute is called.
function waitExecute (app, waitListen) {
... // other code
area.removeEventListener('click', waitListen, true);
}
define your Event Handler first,
and then
area.addEventListener('click',handler);
area.removeEventListener('click',handler);
This is what I ended up doing but it's in a route class but should not make much difference, I wanted for the event listener not to accumulate each time afterModel() is called but also needed arguments and scope so that the model is changed each time.
export default class iFrameRoute extends Route {
afterModel(model) {
this.initFrame = function(event) {
alert("I am being called");
window.removeEventListener("message", this.route.test);
}.bind({route: this, data: model});
window.addEventListener("message", this.initFrame );
}
}
I went through this same problem recently. A reasonble solution that I found was remove attribute "onclick" on element from HTMLElement class.
Let's imagine that you already got your component from DOM - using document.getElementById or document.querySelector - you can try that code:
js
const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');
html example
<div onClick="memoryGame.flipCard(this)">
.... // children elements
</div>
I know which this solution it ins't the best, but it works!
I hope I was able to help you.
Cheers!
PS: please, give me a "useful answer"... thanks :D
while adding function store in array and removing pass by map work for me
const [functionObjects, setfunctionObjects] = useState([]);
const addListener = (beforeUnloadListener) =>{
setfunctionObjects([...nano, beforeUnloadListener]);
addEventListener("beforeunload", beforeUnloadListener, {capture: true});
};
const removeListener = (beforeUnloadListener) => {
functionObjects.map((item) => {
removeEventListener("beforeunload", item, {capture: true});});
};
In case of React we can use useRef() to store our listener function in current property. So that in case of re-render and in case of remove listener it will maintain the same reference to the function.
const handleWindowClick = useRef(() => {
console.log("window clicked");
});
// for attaching event listener
window.addEventListener("click", handleWindowClick.current);
// for detaching event listener
window.removeEventListener("click", handleWindowClick.current);
Update 2023
I was using Angular and after numerous tries using AbortController() nothing solved my problem.
Finally renderer2 to the rescue. Here's what I did
mouseMoveListener :any;
mouseUpListener :any;
this.mouseMoveListener = this._renderer.listen("document", "mousemove", (event) => {
this.onMouseMove(event);
});
this.mouseUpListener = this._renderer.listen("document", "mouseup", (event) => {
this.onMouseUp(event);
});
ngOnDestroy(){
this.mouseMoveListener();
this.mouseUpListener();
}
I know (mostly) how to react to the various events fired by that menagerie of objects living inside the DOM.
To notch things up a bit, I'd like to be able to fire my own bespoke events when appropriate, something I suppose I could pseudo-code as follow :
myObject = {
prop:{ soAndSo }
method : function(args){
//do some stuff that takes forever
"All done and ready, now tell the world"
}
}
The idea being of course that some time down the road I can instantiate a myObject, then even later monitor its behaviour with something to the effect of
aMyObject.onmyevent = function(event){
//do something appropriate for the circumstance
}
The thing is, I have no clue as to where to start regarding the part "now tell the world".
Will you point me in the right direction?
You need to create a faux eventEmitter. Here is one I made while following a tutorial from Pluralsight called React and Flux for Angular Developers: Tutorial
To your question, you 'tell the world' by emitting the event, which is essentially calling all the active listeners you have.
// make your own emitter:
function EventEmitter () {
// holds events buy type; ex:'ADD_TODO'
this._events = {};
}
EventEmitter.prototype.on = function(type, listener) {
// add events to listen for
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
};
EventEmitter.prototype.emit = function(type) {
// emit event base on type
if (this._events[type]) {
this._event[type].forEach(function(listener) {
// call listeners for events:
listener();
});
}
};
EventEmitter.prototype.removeListener = function(type, listern) {
if (this._events[type]) {
this._events[type].splice(this._events[type].indexOf(listener), 1)
}
};
There are 24 div-objects waiting/listening for a mouse-click. After click on one div-object, I want to remove the EventListener from all 24 div-objects.
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener()//Problem lies here
}
//Some other code to be run after mouseclick
},false);
}
The problem is that the removeEventListener is nested in addEventListener and I need to define type, listener, caption as attributes to the removeEventListener method. And I think it is impossible to define the listener because of nesting.
I also tried to define a function name, but it didn't worked:
for (var i=1;i<=24;i++){
document.getElementById('div'+i).addEventListener('click',function helpme(event){
for (var z=1;z<=24;z++){
document.getElementById('div'+z).removeEventListener('click',helpme,false);
}
//Some other code to be run after mouseclick
},false);
}
You can tell the event listener to simply fire just once:
document.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
The documentation says:
once:
A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
It should work with a named function. If your second approach does not work properly, try storing the initial listener into a variable, like this:
var handler = function(event) {
for(...) {
removeEventListener('click', handler, false);
}
};
addEventListener('click', handler, false);
Ps. if you care about speed, you may wish to consider using just one event handler. You can put the handler into the parent element of the divs, and then delegate the event from there. With 24 handlers your current approach probably doesn't have a very big performance hit, but this is something you should keep in mind if it ever feels slow.
For those who needs to remove after a certain condition (or even inside a loop too), one alternative is using AbortController and AbortSignal:
const abortController = new AbortController();
let handler = function(event) {
if(...) {
abortController.abort();
}
};
addEventListener('click', handler, {signal: abortController.signal});
The same answer:
element.addEventListener("click", (e) => {
// function which to run on event
}, { once: true });
You can read more here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener