How to unsubscribe from a socket.io subscription? - javascript

Suppose there are objects making subscriptions to a socket server like so:
socket.on('news', obj.socketEvent)
These objects have a short life span and are frequently created, generating many subscriptions. This seems like a memory leak and an error prone situation which would intuitively be prevented this way:
socket.off('news', obj.socketEvent)
before the object is deleted, but alas, there isn't an off method in the socket. Is there another method meant for this?
Edit: having found no answer I'm assigning a blank method to overwrite the wrapper method for the original event handler, an example follows.
var _blank = function(){};
var cbProxy = function(){
obj.socketEvent.apply(obj, arguments)
};
var cbProxyProxy = function(){
cbProxy.apply ({}, arguments)
}
socket.on('news', cbProxyProxy);
// ...and to unsubscribe
cbProxy = _blank;

From looking at the source of socket.io.js (couldn't find it in documentation anywhere), I found these two functions:
removeListener = function(name, fn)
removeAllListeners = function(name)
I used removeAllListeners successfully in my app; you should be able to choose from these:
socket.removeListener("news", cbProxy);
socket.removeAllListeners("news");
Also, I don't think your solution of cbProxy = _blank would actually work; that would only affect the cbProxy variable, not any actual socket.io event.

If you want to create listeners that "listens" only once use socket.once('news',func). Socket.io automatically will distroy the listener after the event happened - it's called "volatile listener".

Looking at the code of current version of Socket.io Client (1.4.8) it seems that off, removeAllListeners, removeEventListener are all pointing to the same function.
Calling any of those, providing event name and/or callback, gives the desired result. Not providing anything at all seems to reset everything.
Please do be cautious about the fn/callback argument. It has to be the same instance used in the code.
Example:
var eventCallback = function(data) {
// do something nice
};
socket.off('eventName', eventCallback);
Would work as expected.
Example (will also work):
function eventCallback(data) {
// do something nice
}
socket.off('eventName', eventCallback);
Please be cautious that the callback you are trying to remove is the one that you passed in (this one can bring a lot of confusion and frustration).
This example implements a wrapper around initial callback, trying to remove that would not work as the real callback being added is an undisclosed closure instance: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
Here is the link to that specific line in the codebase: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597

Socket.io version 0.9.16 implements removeListener but not off.
You can use removeListener instead of off when unsubscribing, or simply implement off as follows:
var socket = io.connect(url);
socket.off = socket.removeListener;
If you are using the Backbone listenTo event subscription approach, you'll need to implement the above as Backbone calls off when unsubscribing events.

I found that in socket.io 0.9.11 and Chrome24 socket.io removeListener doesn't work.
this modified version works for me:
EventEmitter.prototype.removeListener = function (name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (io.util.isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i].toString() === fn.toString() || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else {
if (list.toString() === fn.toString() || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
}
return this;
};

Since I had a spot of troubles making this work figured I'd chime in here as well, along with a nice updated answer for 2017. Thanks to #Pjotr for pointing out that it has to be the same callback instance.
Example with Angular2 TypeScript in a socket-io.subscriber service. Note the "newCallback" wrapper
private subscriptions: Array<{
key: string,
callback: Function
}>;
constructor() {
this.subscriptions = [];
}
subscribe(key: string, callback: Function) {
let newCallback = (response) => callback(response);
this.socket.on(key, newCallback);
return this.subscriptions.push({key: key, callback: newCallback}) - 1;
}
unsubscribe(i: number) {
this.socket.removeListener(this.subscriptions[i].key, this.subscriptions[i].callback);
}

Removing an event listener on the client
var Socket = io.connect();
Socket.removeListener('test', test);

Also on java client, it can be done the same way with the Javascript client. I've pasted from socket.io.
// remove all listeners of the connect event
socket.off(Socket.EVENT_CONNECT);
listener = new Emitter.Listener() { ... };
socket.on(Socket.EVENT_CONNECT, listener);
// remove the specified listener
socket.off(Socket.EVENT_CONNECT, listener);

Pre-store the events using an array, and by the time you need to unsubscribe them, use the off method, which is a built in method from socket.io:
// init
var events = []
// store
events.push("eventName")
// subscribe
socket.on("eventName", cb)
// remove
events = events.filter(event => event!="eventName")
// unsubscribe
socket.off("eventName")

To add to #Andrew Magee, here is an example of unsubscribing socket.io events in Angular JS, and of course works with Vanilla JS:
function handleCarStarted ( data ) { // Do stuff }
function handleCarStopped ( data ) { // Do stuff }
Listen for events:
var io = $window.io(); // Probably put this in a factory, not controller instantiation
io.on('car.started', handleCarStarted);
io.on('car.stopped', handleCarStopped);
$scope.$on('$destroy', function () {
io.removeListener('car.started', handleCarStarted);
io.removeListener('car.stopped', handleCarStopped);
});

This has helped me in both Angular 8 and React 16.8:
receiveMessage() {
let newCallback = (data) => {
this.eventEmitter.emit('add-message-response', data);
};
this.socket.on('add-message-response', newCallback);
this.subscriptions.push({key: 'add-message-response', callback: newCallback});
}
receiveMessageRemoveSocketListener() {
this.findAndRemoveSocketEventListener('add-message-response');
}
findAndRemoveSocketEventListener (eventKey) {
let foundListener = this.subscriptions.find( (subscription) => subscription.key === eventKey );
if(!foundListener) {
return;
}
this.socket.removeListener(foundListener.key, foundListener.callback);
this.subscriptions = this.subscriptions.filter( (subscription) => subscription.key !== eventKey );
}
Reason for using an Array of Subscriptions is that when you Subscribe to an event multiple times and you don't remove an unsubscribed subscription from the Subscription list you will most probably be right at first time you remove the subscription from the list, but later subscriptions will not be removed as you will be finding first instance only every time you unsubscribe the event.
You can simply call receiveMessage(); to subscribe to an the event and receiveMessageRemoveSocketListener(); to Unsubscribe.

Related

Remove all listener of "document" element [duplicate]

Just question: Is there any way to completely remove all events of an object, e.g. a div?
EDIT: I'm adding per div.addEventListener('click',eventReturner(),false); an event.
function eventReturner() {
return function() {
dosomething();
};
}
EDIT2: I found a way, which is working, but not possible to use for my case:
var returnedFunction;
function addit() {
var div = document.getElementById('div');
returnedFunction = eventReturner();
div.addEventListener('click',returnedFunction,false); //You HAVE to take here a var and not the direct call to eventReturner(), because the function address must be the same, and it would change, if the function was called again.
}
function removeit() {
var div = document.getElementById('div');
div.removeEventListener('click',returnedFunction,false);
}
I am not sure what you mean with remove all events. Remove all handlers for a specific type of event or all event handlers for one type?
Remove all event handlers
If you want to remove all event handlers (of any type), you could clone the element and replace it with its clone:
var clone = element.cloneNode(true);
Note: This will preserve attributes and children, but it will not preserve any changes to DOM properties.
Remove "anonymous" event handlers of specific type
The other way is to use removeEventListener() but I guess you already tried this and it didn't work. Here is the catch:
Calling addEventListener to an anonymous function creates a new listener each time. Calling removeEventListener to an anonymous function has no effect. An anonymous function creates a unique object each time it is called, it is not a reference to an existing object though it may call one. When adding an event listener in this manner be sure it is added only once, it is permanent (cannot be removed) until the object it was added to, is destroyed.
You are essentially passing an anonymous function to addEventListener as eventReturner returns a function.
You have two possibilities to solve this:
Don't use a function that returns a function. Use the function directly:
function handler() {
dosomething();
}
div.addEventListener('click',handler,false);
Create a wrapper for addEventListener that stores a reference to the returned function and create some weird removeAllEvents function:
var _eventHandlers = {}; // somewhere global
const addListener = (node, event, handler, capture = false) => {
if (!(event in _eventHandlers)) {
_eventHandlers[event] = []
}
// here we track the events and their nodes (note that we cannot
// use node as Object keys, as they'd get coerced into a string
_eventHandlers[event].push({ node: node, handler: handler, capture: capture })
node.addEventListener(event, handler, capture)
}
const removeAllListeners = (targetNode, event) => {
// remove listeners from the matching nodes
_eventHandlers[event]
.filter(({ node }) => node === targetNode)
.forEach(({ node, handler, capture }) => node.removeEventListener(event, handler, capture))
// update _eventHandlers global
_eventHandlers[event] = _eventHandlers[event].filter(
({ node }) => node !== targetNode,
)
}
And then you could use it with:
addListener(div, 'click', eventReturner(), false)
// and later
removeAllListeners(div, 'click')
DEMO
Note: If your code runs for a long time and you are creating and removing a lot of elements, you would have to make sure to remove the elements contained in _eventHandlers when you destroy them.
This will remove all listeners from children but will be slow for large pages. Brutally simple to write.
element.outerHTML = element.outerHTML;
Use the event listener's own function remove(). For example:
getEventListeners().click.forEach((e)=>{e.remove()})
As corwin.amber says, there are differences between Webkit an others.
In Chrome:
getEventListeners(document);
Which gives you an Object with all the existing event listeners:
Object
click: Array[1]
closePopups: Array[1]
keyup: Array[1]
mouseout: Array[1]
mouseover: Array[1]
...
From here you can reach the listener you want to remove:
getEventListeners(document).copy[0].remove();
So All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { o.remove(); }
)
}
In Firefox
Is a little bit different because it uses a listener wrapper that contains no remove function. You have to get the listener you want to remove:
document.removeEventListener("copy", getEventListeners(document).copy[0].listener)
All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { document.removeEventListener(eventType, o.listener) }
)
}
I stumbled with this post trying to disable the annoying copy protection of a news website.
Enjoy!
You can add a hook function to intercept all calls to addEventHandler. The hook will push the handler to a list that can be used for cleanup. For example,
if (EventTarget.prototype.original_addEventListener == null) {
EventTarget.prototype.original_addEventListener = EventTarget.prototype.addEventListener;
function addEventListener_hook(typ, fn, opt) {
console.log('--- add event listener',this.nodeName,typ);
this.all_handlers = this.all_handlers || [];
this.all_handlers.push({typ,fn,opt});
this.original_addEventListener(typ, fn, opt);
}
EventTarget.prototype.addEventListener = addEventListener_hook;
}
You should insert this code near the top of your main web page (e.g. index.html). During cleanup, you can loop thru all_handlers, and call removeEventHandler for each. Don't worry about calling removeEventHandler multiple times with the same function. It is harmless.
For example,
function cleanup(elem) {
for (let t in elem) if (t.startsWith('on') && elem[t] != null) {
elem[t] = null;
console.log('cleanup removed listener from '+elem.nodeName,t);
}
for (let t of elem.all_handlers || []) {
elem.removeEventListener(t.typ, t.fn, t.opt);
console.log('cleanup removed listener from '+elem.nodeName,t.typ);
}
}
Note: for IE use Element instead of EventTarget, and change => to function, and various other things.
Clone the element and replace the element with its clone. Events are not cloned.
elem.replaceWith(elem.cloneNode(true));
This uses Node.cloneNode() to clone the elem DOM object, which ignores all event handlers (though, as Jan Turoň's answer notes, attributes like onclick="…" will remain). It then uses Element.replaceWith() to replace elem with that clone. Simple assignment to an anonymous clone wasn't working for me.
This should be faster and cleaner than redefining elem.outerHTML with itself (as proposed by pabombs's answer) but may be slower than answers that iterate through and purge each listener (noting that getEventListeners() seems available exclusively in Chrome's dev console—not elsewhere in Chrome, not at all on Firefox). Presumably, at some higher volume of listeners to clear, this non-loop solution becomes faster.
(This is a simplification of Felix Kling's answer with help from asheroto's comment to it.)
you can add function and remove all other click by assign them
btn1 = document.querySelector(".btn-1")
btn1.addEventListener("click" , _=>{console.log("hello")})
btn1.addEventListener("click" , _=>{console.log("How Are you ?")})
btn2 = document.querySelector(".btn-2")
btn2.onclick = _=>{console.log("Hello")}
btn2.onclick = _=>{console.log("Bye")}
<button class="btn-1">Hello to Me</button>
<button class="btn-2">Hello to Bye</button>
You can indeed remove all event handlers by cloning the node as #FelixKling suggests in his answer, however don't forget that
attribute event handlers are not affected by cloning
Having element like this
<div id="test" onclick="alert(42)">test</div>
will still alert on click after cloning. To remove this sort of events, you need to use removeAttribute method, in general
const removeAttEvents = el =>
[...el.attributes].forEach(att =>
att.name.startsWith("on") && el.removeAttribute(att.name)
);
Then having the test element above, calling removeAttEvents(test) gets rid of the click handler.
To complete the answers, here are real-world examples of removing events when you are visiting websites and don't have control over the HTML and JavaScript code generated.
Some annoying websites are preventing you to copy-paste usernames on login forms, which could easily be bypassed if the onpaste event was added with the onpaste="return false" HTML attribute.
In this case we just need to right click on the input field, select "Inspect element" in a browser like Firefox and remove the HTML attribute.
However, if the event was added through JavaScript like this:
document.getElementById("lyca_login_mobile_no").onpaste = function(){return false};
We will have to remove the event through JavaScript also:
document.getElementById("lyca_login_mobile_no").onpaste = null;
In my example, I used the ID "lyca_login_mobile_no" since it was the text input ID used by the website I was visiting.
Another way to remove the event (which will also remove all the events) is to remove the node and create a new one, like we have to do if addEventListener was used to add events using an anonymous function that we cannot remove with removeEventListener.
This can also be done through the browser console by inspecting an element, copying the HTML code, removing the HTML code and then pasting the HTML code at the same place.
It can also be done faster and automated through JavaScript:
var oldNode = document.getElementById("lyca_login_mobile_no");
var newNode = oldNode.cloneNode(true);
oldNode.parentNode.insertBefore(newNode, oldNode);
oldNode.parentNode.removeChild(oldNode);
Update: if the web app is made using a JavaScript framework like Angular, it looks the previous solutions are not working or breaking the app.
Another workaround to allow pasting would be to set the value through JavaScript:
document.getElementById("lyca_login_mobile_no").value = "username";
At the moment, I don't know if there is a way to remove all form validation and restriction events without breaking an app written entirely in JavaScript like Angular.
Update 2: There is also a way to remove a specific event that was added with addEventListener on a website we don't own, by using the getEventListeners function combined to removeEventListener like mentioned in the answer of Jmakuc. If getEventListeners does not exist like on Firefox, you can use a polyfill and inject the script on the page with Greasemonkey addon: https://github.com/colxi/getEventListeners/issues/1
The only easy way I found and worked is this:
Let's say we want to add 2 event listeners
const element = document.getElementById("element");
element.addEventListener('mouseover',
()=>{
// some task
});
element.addEventListener('mouseout',
()=>{
// some task
});
Now you can remove both of the elements by simply:
element.replaceWith(element.cloneNode(true));
Removing all the events on document:
One liner:
for (key in getEventListeners(document)) { getEventListeners(document)[key].forEach(function(c) { c.remove() }) }
Pretty version:
for (key in getEventListeners(document)) {
getEventListeners(document)[key].forEach(function(c) {
c.remove()
})
}
angular has a polyfill for this issue, you can check. I did not understand much but maybe it can help.
const REMOVE_ALL_LISTENERS_EVENT_LISTENER = 'removeAllListeners';
proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function () {
const target = this || _global;
const eventName = arguments[0];
if (!eventName) {
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
let evtName = match && match[1];
// in nodejs EventEmitter, removeListener event is
// used for monitoring the removeListener call,
// so just keep removeListener eventListener until
// all other eventListeners are removed
if (evtName && evtName !== 'removeListener') {
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, evtName);
}
}
// remove removeListener listener finally
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, 'removeListener');
}
else {
const symbolEventNames = zoneSymbolEventNames$1[eventName];
if (symbolEventNames) {
const symbolEventName = symbolEventNames[FALSE_STR];
const symbolCaptureEventName = symbolEventNames[TRUE_STR];
const tasks = target[symbolEventName];
const captureTasks = target[symbolCaptureEventName];
if (tasks) {
const removeTasks = tasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
if (captureTasks) {
const removeTasks = captureTasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
}
}
if (returnTarget) {
return this;
}
};
....
You can add a helper function that clears event listener for example
function clearEventListener(element) {
const clonedElement = element.cloneNode(true);
element.replaceWith(clonedElement);
return clonedElement;
}
just pass in the element to the function and that's it...
Sub-class of EventTarget from the JavaScript WebAPI. Supports removing events without specifying a handler function reference.
class SmartEventTarget extends EventTarget {
constructor() {
super();
this.handlers = {};
}
addEventListener(name, handler) {
super.addEventListener(name, handler);
if (!this.handlers[name]) {
this.handlers[name] = new Set();
}
this.handlers[name].add(handler);
}
removeEventListener(name, handler) {
if (handler) {
super.removeEventListener(name, handler);
this.handlers[name].delete(handler);
} else {
this.handlers[name].forEach(h => {
super.removeEventListener(name, h)
});
this.handlers[name].clear();
}
}
removeAllListeners(name) {
if (name) {
this.removeEventListener(name, null);
} else {
Object.keys(this.handlers).map(name => {
this.removeEventListener(name, null);
});
this.handlers = {};
}
}
}
See this Gist for unit tests. You can run the tests by simply copying the code from the Gist into your browser JS console and pressing enter.
Be sure to read strange JS from the internet before blindly pasting it into your console.
https://gist.github.com/angstyloop/504414aba95b61b98be0db580cb2a3b0
I know this is an old question but for me the only thing that worked was:
parentOfTheElement.innerHTML = parentOfTheElement.innerHTML;
While the other solutions do in fact remove all the listeners, I had problems adding new ones when using either the outerHTML trick or cloneNode()
May be the browser will do it for you if you do something like:
Copy the div and its attributes and insert it before the old one, then move the content from the old to the new and delete the old?
One method is to add a new event listener that calls e.stopImmediatePropagation().
var div = getElementsByTagName('div')[0]; /* first div found; you can use getElementById for more specific element */
div.onclick = null; // OR:
div.onclick = function(){};
//edit
I didn't knew what method are you using for attaching events. For addEventListener you can use this:
div.removeEventListener('click',functionName,false); // functionName is the name of your callback function
more details

How to get event name in node.js event listener?

I am dynamically creating event listener for some events. In that event listener i want to do another emit call based on the event name, so i want to get the event listener name
I am using node.js eventemitter.
var events = require('events').EventEmitter;
var util = require('util');
.....
.....
for(i in events) {
transport.on(events[i],function(userId) {
eventName = events[i];
var acct = accountsList[userId];
if(acct) {
acct.emit(events[i],userId);
}
});
}
The above method is working but the problem line is
acct.emit(events[i],userId);
that events[i] is having last value of the loop. so if received any event it always emitting the final loop of the events[i] value...
So you are preserving value of event_name in a closure. It is legal, but doesn't look very neat.
Instead, you could use EventEmitter2 module like this
var EventEmitter = require('eventemitter2').EventEmitter2;
var emitter = new EventEmitter();
emitter.on('ev1', function (line) {
console.log(this.event); // here's your fired event
});
emitter.emit('ev1', 'has fired');
Check out the documentation, you could do much more than the original EventEmitter
i overcome this by function ...but i want to know this right way or not...
for(i in events) {
function test(event_name){
transport.purpleEvents.on(event_name,function(userId) {
var acct = accountsList[userId];
if(acct) {
acct.emit(event_name,userId);
}
});
}
test(events[i]);
}
The for loop you are using is async and results in all calls using the last value in events. If you replace that with forEach it will run sync. Try something like this untested code:
events.forEach(function(i) {
transport.on(events[i],function(userId) {
eventName = events[i];
var acct = accountsList[userId];
if(acct) {
acct.emit(events[i],userId);
}
});
});
you can use a closure like this which can be practical soln..
for(i in events) {
transport.on(events[i],closureplusopn(events[i]))
}
function closureplusopn(eventName){
return function(userID){
var acct = accountsList[userId];
if(acct) {
acct.emit(eventName,userID);
}
}
}
You can pass the event name through the context param.
On EventEmitter3 the .on function params are (eventName, functionName, context).
What helped me figure out how to get the eventName in the callback function was to pass this string as context and access it with the this keyword. Hope this helps whoever comes to this question and has something like this as an issue.

YUI3 custom async events not working on Y.Global

I am trying implement async event leveraging YUI3 library. So the application had been notified about event passed even with late subscription, simular like load or ready events do.
Here it is what I have so far, but no luck around.
YUI().use('event', 'event-custom', function(Y){
function onCustomEvent () {
Y.Global.on('custom:event', function(){
alert('custom fired');
});
}
window.setTimeout(onCustomEvent, 2000);
});
YUI().use('event', 'event-custom', function(Y){
Y.publish('custom:event', {
emitFacade: true,
broadcast: 2,
fireOnce: true,
async: true
});
function fireCustomEvent () {
Y.Global.fire('custom:event');
}
window.setTimeout(fireCustomEvent, 1000);
});
If anyone could give a hint what's wrong with this code? Thank you.
UPD:
After a bit investigations it turns out that async events work fine inside one use() instance and when not using Global broadcasting. So that's something either bug or limitation. Still discovering
Okay, at the high level the inconsistency with global events (how I understood it) lays in the sandbox nature of Y object. So at some point you could fire only sync events globally cause async parameters you subscribe to custom event made on Y instance and not passed further (and than YUI uses some defaults or whatever). This possibly makes sense but than why such kind of events should be fireable globally? Either I miss some substantial part of YUI and this is candidate for bug report.
Anyway I do not have time to dive deeper in YUI and what I really practically need could be wrapped in 40 lines of code:
YUI.add('async-pubsub', function(Y) {
var subscribers = {};
if ( !YUI.asyncPubSub ) {
YUI.namespace('asyncPubSub');
YUI.asyncPubSub = (function(){
var eventsFired = {};
function doPublishFor(name) {
var subscriber;
for ( subscriber in subscribers ) {
if ( subscriber === name ) {
(subscribers[name]).call();
delete ( subscribers[name] ); // Keep Planet clean
}
}
}
return {
'publish': function(name, options) {
eventsFired[name] = options || {};
doPublishFor(name);
},
'subscribe': function(name, callback) {
if ( subscribers[name] ) {
Y.log('More than one async subscriber per instance, overriding it.', 'warning', 'async-pubsub');
}
subscribers[name] = callback || function() {};
if ( eventsFired[name] ) {
window.setTimeout(
function () {
doPublishFor(name);
},0
);
}
}
};
})();
}
Y.asyncPubSub = YUI.asyncPubSub;
}, '1.0', {requires: []});
There is some limitation and room for optimization here, like ability subscribe only one action for one event per use instance, but I do not need more. I will also try to debug and enhance this snippet in future if there will be interest.
Still curious about YUI behavior, is it bug or something?

Waiting for multiple Events

I'm currently developing an app mainly for self-education purposes and since I'm still not completely used to js I could use some help for my problem:
In my app I'm using a Javascript library (jqMobi) which is used for DOM manipulation, page transitions, ajax calls etc and I'm also using phonegap to access device features such as the geolocation.
When I start up my app I want to get the geolocation of the device, send an ajax (jsonp) request to my server (including the geolocation of the device) which returns an array of JSON objects which I will use to build up a list.
Before I can get the geolocation I need to wait for phonegap to load. And before using jqMobi to make the ajax call and handle the response I need to wait for it to load as well.
So I basically have to events im listening to
document.addEventListener("DOMContentLoaded",execute_this,false); //jqMobi is now ready
document.addEventListener("deviceready", execure_sth, false); //Phonegap is now ready
How do I execute a function as soon as both of these events have fired and not before?
If I'd use jQuery I'd make use of its $.Deferred objects and its When ... Then Function but since I don't have access to these I'm looking for an alternative.
At first blush, something like this would definitely work:
var executed_this = false, executed_sth = false;
function execute_this() {
executed_this = true;
combined_execution();
}
function execute_sth() {
executed_sth = true;
combined_execution();
}
function combined_execution() {
if (executed_this && executed_sth) {
// magic!
}
}
But is not extensible (what if you want a third event to wait on?). A counter would work:
var wait_on = 2;
function execute_this() {
combined_execution();
}
function execute_sth() {
combined_execution();
}
function combined_execution() {
wait_on--;
if (wait_on === 0) {
// magic!
}
}
Is more extensible, but that assumes that the events only fire once. Either way, these are the primatives that can control the type of flow control you are asking for, and everything else is (for the most part) a higher level abstraction on these two.
You can make Promises that resolve when the events fire, and wait for both of them to be ready.
var dcl = new Promise(function(resolve) {
document.addEventListener("DOMContentLoaded",resolve,false);
})
var deviceready = new Promise(function(resolve) {
document.addEventListener("deviceready", resolve, false);
})
Promise.all([dcl, deviceready]).then(function() {
//both are ready
});
Try this,
document.addEventListener("DOMContentLoaded",execute_this,false);
function execute_this(){
document.addEventListener("deviceready", execure_sth, false);
}
function execute_sth(){
//your code here
}
Merged a few of the answers here into waitForEvents(eventTarget, eventNames);
Usage:
waitForEvents(window, 'DOMContentLoaded,deviceready').then(function() {
// do stuff
});
Source:
/**
* Wait for multiple DOM events to fire
* #param {object} eventTarget - element to listen for events on (default document)
* #param {Array<string>|string} eventNames - array or csv string of event names
* #returns {Promise<Array>} resolves with array of event objects
*/
function waitForEvents(eventTarget, eventNames) {
eventTarget = eventTarget || document;
eventNames = (!Array.isArray(eventNames)) ? String(eventNames).split(',') : eventNames;
// clean event names
eventNames = eventNames.map(function(item) {
return String(item).trim();
})
.filter(function(item) {
return item !== '';
});
var items = [];
// create a promise to wait for each event
var listeners = eventNames.map(function(eventName) {
return new Promise(function(resolve) {
eventTarget.addEventListener(eventName, function(e) {
items.push(e);
resolve();
}, false);
});
});
// resolve once all events have fired
return Promise.all(listeners).then(function() {
return Promise.resolve(items);
});
}
Gist on GitHub
Promise.all is the way to go, but for fun (and laziness at importing a polyfill), I came up with this little solution:
executedCount = 0
function onExecuted() {
document.body.dispatchEvent(new Event('executedCountIs'+(++executedCount)));
}
function execute_this() {
onExecuted()
}
function execute_sth() {
onExecuted()
}
function main() {
document.body.addEventListener('executedCountIs2', doStuffWhenBothAreReady)
execute_this()
execute_sth()
}
I'm assuming the Javascript engine protects me from a race condition here.

Javascript/DOM: How to remove all event listeners of a DOM object?

Just question: Is there any way to completely remove all events of an object, e.g. a div?
EDIT: I'm adding per div.addEventListener('click',eventReturner(),false); an event.
function eventReturner() {
return function() {
dosomething();
};
}
EDIT2: I found a way, which is working, but not possible to use for my case:
var returnedFunction;
function addit() {
var div = document.getElementById('div');
returnedFunction = eventReturner();
div.addEventListener('click',returnedFunction,false); //You HAVE to take here a var and not the direct call to eventReturner(), because the function address must be the same, and it would change, if the function was called again.
}
function removeit() {
var div = document.getElementById('div');
div.removeEventListener('click',returnedFunction,false);
}
I am not sure what you mean with remove all events. Remove all handlers for a specific type of event or all event handlers for one type?
Remove all event handlers
If you want to remove all event handlers (of any type), you could clone the element and replace it with its clone:
var clone = element.cloneNode(true);
Note: This will preserve attributes and children, but it will not preserve any changes to DOM properties.
Remove "anonymous" event handlers of specific type
The other way is to use removeEventListener() but I guess you already tried this and it didn't work. Here is the catch:
Calling addEventListener to an anonymous function creates a new listener each time. Calling removeEventListener to an anonymous function has no effect. An anonymous function creates a unique object each time it is called, it is not a reference to an existing object though it may call one. When adding an event listener in this manner be sure it is added only once, it is permanent (cannot be removed) until the object it was added to, is destroyed.
You are essentially passing an anonymous function to addEventListener as eventReturner returns a function.
You have two possibilities to solve this:
Don't use a function that returns a function. Use the function directly:
function handler() {
dosomething();
}
div.addEventListener('click',handler,false);
Create a wrapper for addEventListener that stores a reference to the returned function and create some weird removeAllEvents function:
var _eventHandlers = {}; // somewhere global
const addListener = (node, event, handler, capture = false) => {
if (!(event in _eventHandlers)) {
_eventHandlers[event] = []
}
// here we track the events and their nodes (note that we cannot
// use node as Object keys, as they'd get coerced into a string
_eventHandlers[event].push({ node: node, handler: handler, capture: capture })
node.addEventListener(event, handler, capture)
}
const removeAllListeners = (targetNode, event) => {
// remove listeners from the matching nodes
_eventHandlers[event]
.filter(({ node }) => node === targetNode)
.forEach(({ node, handler, capture }) => node.removeEventListener(event, handler, capture))
// update _eventHandlers global
_eventHandlers[event] = _eventHandlers[event].filter(
({ node }) => node !== targetNode,
)
}
And then you could use it with:
addListener(div, 'click', eventReturner(), false)
// and later
removeAllListeners(div, 'click')
DEMO
Note: If your code runs for a long time and you are creating and removing a lot of elements, you would have to make sure to remove the elements contained in _eventHandlers when you destroy them.
This will remove all listeners from children but will be slow for large pages. Brutally simple to write.
element.outerHTML = element.outerHTML;
Use the event listener's own function remove(). For example:
getEventListeners().click.forEach((e)=>{e.remove()})
As corwin.amber says, there are differences between Webkit an others.
In Chrome:
getEventListeners(document);
Which gives you an Object with all the existing event listeners:
Object
click: Array[1]
closePopups: Array[1]
keyup: Array[1]
mouseout: Array[1]
mouseover: Array[1]
...
From here you can reach the listener you want to remove:
getEventListeners(document).copy[0].remove();
So All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { o.remove(); }
)
}
In Firefox
Is a little bit different because it uses a listener wrapper that contains no remove function. You have to get the listener you want to remove:
document.removeEventListener("copy", getEventListeners(document).copy[0].listener)
All the event listeners:
for(var eventType in getEventListeners(document)) {
getEventListeners(document)[eventType].forEach(
function(o) { document.removeEventListener(eventType, o.listener) }
)
}
I stumbled with this post trying to disable the annoying copy protection of a news website.
Enjoy!
You can add a hook function to intercept all calls to addEventHandler. The hook will push the handler to a list that can be used for cleanup. For example,
if (EventTarget.prototype.original_addEventListener == null) {
EventTarget.prototype.original_addEventListener = EventTarget.prototype.addEventListener;
function addEventListener_hook(typ, fn, opt) {
console.log('--- add event listener',this.nodeName,typ);
this.all_handlers = this.all_handlers || [];
this.all_handlers.push({typ,fn,opt});
this.original_addEventListener(typ, fn, opt);
}
EventTarget.prototype.addEventListener = addEventListener_hook;
}
You should insert this code near the top of your main web page (e.g. index.html). During cleanup, you can loop thru all_handlers, and call removeEventHandler for each. Don't worry about calling removeEventHandler multiple times with the same function. It is harmless.
For example,
function cleanup(elem) {
for (let t in elem) if (t.startsWith('on') && elem[t] != null) {
elem[t] = null;
console.log('cleanup removed listener from '+elem.nodeName,t);
}
for (let t of elem.all_handlers || []) {
elem.removeEventListener(t.typ, t.fn, t.opt);
console.log('cleanup removed listener from '+elem.nodeName,t.typ);
}
}
Note: for IE use Element instead of EventTarget, and change => to function, and various other things.
Clone the element and replace the element with its clone. Events are not cloned.
elem.replaceWith(elem.cloneNode(true));
This uses Node.cloneNode() to clone the elem DOM object, which ignores all event handlers (though, as Jan Turoň's answer notes, attributes like onclick="…" will remain). It then uses Element.replaceWith() to replace elem with that clone. Simple assignment to an anonymous clone wasn't working for me.
This should be faster and cleaner than redefining elem.outerHTML with itself (as proposed by pabombs's answer) but may be slower than answers that iterate through and purge each listener (noting that getEventListeners() seems available exclusively in Chrome's dev console—not elsewhere in Chrome, not at all on Firefox). Presumably, at some higher volume of listeners to clear, this non-loop solution becomes faster.
(This is a simplification of Felix Kling's answer with help from asheroto's comment to it.)
you can add function and remove all other click by assign them
btn1 = document.querySelector(".btn-1")
btn1.addEventListener("click" , _=>{console.log("hello")})
btn1.addEventListener("click" , _=>{console.log("How Are you ?")})
btn2 = document.querySelector(".btn-2")
btn2.onclick = _=>{console.log("Hello")}
btn2.onclick = _=>{console.log("Bye")}
<button class="btn-1">Hello to Me</button>
<button class="btn-2">Hello to Bye</button>
You can indeed remove all event handlers by cloning the node as #FelixKling suggests in his answer, however don't forget that
attribute event handlers are not affected by cloning
Having element like this
<div id="test" onclick="alert(42)">test</div>
will still alert on click after cloning. To remove this sort of events, you need to use removeAttribute method, in general
const removeAttEvents = el =>
[...el.attributes].forEach(att =>
att.name.startsWith("on") && el.removeAttribute(att.name)
);
Then having the test element above, calling removeAttEvents(test) gets rid of the click handler.
To complete the answers, here are real-world examples of removing events when you are visiting websites and don't have control over the HTML and JavaScript code generated.
Some annoying websites are preventing you to copy-paste usernames on login forms, which could easily be bypassed if the onpaste event was added with the onpaste="return false" HTML attribute.
In this case we just need to right click on the input field, select "Inspect element" in a browser like Firefox and remove the HTML attribute.
However, if the event was added through JavaScript like this:
document.getElementById("lyca_login_mobile_no").onpaste = function(){return false};
We will have to remove the event through JavaScript also:
document.getElementById("lyca_login_mobile_no").onpaste = null;
In my example, I used the ID "lyca_login_mobile_no" since it was the text input ID used by the website I was visiting.
Another way to remove the event (which will also remove all the events) is to remove the node and create a new one, like we have to do if addEventListener was used to add events using an anonymous function that we cannot remove with removeEventListener.
This can also be done through the browser console by inspecting an element, copying the HTML code, removing the HTML code and then pasting the HTML code at the same place.
It can also be done faster and automated through JavaScript:
var oldNode = document.getElementById("lyca_login_mobile_no");
var newNode = oldNode.cloneNode(true);
oldNode.parentNode.insertBefore(newNode, oldNode);
oldNode.parentNode.removeChild(oldNode);
Update: if the web app is made using a JavaScript framework like Angular, it looks the previous solutions are not working or breaking the app.
Another workaround to allow pasting would be to set the value through JavaScript:
document.getElementById("lyca_login_mobile_no").value = "username";
At the moment, I don't know if there is a way to remove all form validation and restriction events without breaking an app written entirely in JavaScript like Angular.
Update 2: There is also a way to remove a specific event that was added with addEventListener on a website we don't own, by using the getEventListeners function combined to removeEventListener like mentioned in the answer of Jmakuc. If getEventListeners does not exist like on Firefox, you can use a polyfill and inject the script on the page with Greasemonkey addon: https://github.com/colxi/getEventListeners/issues/1
The only easy way I found and worked is this:
Let's say we want to add 2 event listeners
const element = document.getElementById("element");
element.addEventListener('mouseover',
()=>{
// some task
});
element.addEventListener('mouseout',
()=>{
// some task
});
Now you can remove both of the elements by simply:
element.replaceWith(element.cloneNode(true));
Removing all the events on document:
One liner:
for (key in getEventListeners(document)) { getEventListeners(document)[key].forEach(function(c) { c.remove() }) }
Pretty version:
for (key in getEventListeners(document)) {
getEventListeners(document)[key].forEach(function(c) {
c.remove()
})
}
angular has a polyfill for this issue, you can check. I did not understand much but maybe it can help.
const REMOVE_ALL_LISTENERS_EVENT_LISTENER = 'removeAllListeners';
proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function () {
const target = this || _global;
const eventName = arguments[0];
if (!eventName) {
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
let evtName = match && match[1];
// in nodejs EventEmitter, removeListener event is
// used for monitoring the removeListener call,
// so just keep removeListener eventListener until
// all other eventListeners are removed
if (evtName && evtName !== 'removeListener') {
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, evtName);
}
}
// remove removeListener listener finally
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, 'removeListener');
}
else {
const symbolEventNames = zoneSymbolEventNames$1[eventName];
if (symbolEventNames) {
const symbolEventName = symbolEventNames[FALSE_STR];
const symbolCaptureEventName = symbolEventNames[TRUE_STR];
const tasks = target[symbolEventName];
const captureTasks = target[symbolCaptureEventName];
if (tasks) {
const removeTasks = tasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
if (captureTasks) {
const removeTasks = captureTasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
}
}
if (returnTarget) {
return this;
}
};
....
You can add a helper function that clears event listener for example
function clearEventListener(element) {
const clonedElement = element.cloneNode(true);
element.replaceWith(clonedElement);
return clonedElement;
}
just pass in the element to the function and that's it...
Sub-class of EventTarget from the JavaScript WebAPI. Supports removing events without specifying a handler function reference.
class SmartEventTarget extends EventTarget {
constructor() {
super();
this.handlers = {};
}
addEventListener(name, handler) {
super.addEventListener(name, handler);
if (!this.handlers[name]) {
this.handlers[name] = new Set();
}
this.handlers[name].add(handler);
}
removeEventListener(name, handler) {
if (handler) {
super.removeEventListener(name, handler);
this.handlers[name].delete(handler);
} else {
this.handlers[name].forEach(h => {
super.removeEventListener(name, h)
});
this.handlers[name].clear();
}
}
removeAllListeners(name) {
if (name) {
this.removeEventListener(name, null);
} else {
Object.keys(this.handlers).map(name => {
this.removeEventListener(name, null);
});
this.handlers = {};
}
}
}
See this Gist for unit tests. You can run the tests by simply copying the code from the Gist into your browser JS console and pressing enter.
Be sure to read strange JS from the internet before blindly pasting it into your console.
https://gist.github.com/angstyloop/504414aba95b61b98be0db580cb2a3b0
I know this is an old question but for me the only thing that worked was:
parentOfTheElement.innerHTML = parentOfTheElement.innerHTML;
While the other solutions do in fact remove all the listeners, I had problems adding new ones when using either the outerHTML trick or cloneNode()
May be the browser will do it for you if you do something like:
Copy the div and its attributes and insert it before the old one, then move the content from the old to the new and delete the old?
One method is to add a new event listener that calls e.stopImmediatePropagation().
var div = getElementsByTagName('div')[0]; /* first div found; you can use getElementById for more specific element */
div.onclick = null; // OR:
div.onclick = function(){};
//edit
I didn't knew what method are you using for attaching events. For addEventListener you can use this:
div.removeEventListener('click',functionName,false); // functionName is the name of your callback function
more details

Categories

Resources