How to clone a Javascript Event object? [duplicate] - javascript

Anybody know how to do a deep copy/cloning of a native javascript event object? I know I can create a new event object and set the appropriate properties manually to match the original event, but it'd be much easier if there's a way to just clone.

Above code will not copy any getters/setters properly. Try:
function cloneEvent(e) {
if (e===undefined || e===null) return undefined;
function ClonedEvent() {};
let clone=new ClonedEvent();
for (let p in e) {
let d=Object.getOwnPropertyDescriptor(e, p);
if (d && (d.get || d.set)) Object.defineProperty(clone, p, d); else clone[p] = e[p];
}
Object.setPrototypeOf(clone, e);
return clone;
}

For your purposes I'd just make it a prototype of a new object constructor and override the ones you want changed. Cloning in JS gets messy due to the circular reference issue so it may not be the quick and dirty solution you were hoping for.
function cloneEventObj(eventObj, overrideObj){
if(!overrideObj){ overrideObj = {}; }
function EventCloneFactory(overProps){
for(var x in overProps){
this[x] = overProps[x];
}
}
EventCloneFactory.prototype = eventObj;
return new EventCloneFactory(overrideObj);
}
//So add your override properties via an object
$el.click(function(e){
var newEventObj = cloneEventObj(
e,
{ target:document.body }
);
doSomething(newEventObj);
});
//or just stick 'em on manually after spitting the object out
/*...
var newEventObj = cloneEventObj(e);
newEventObj.target = document.body
...*/
In this case the 'cloned' object is the prototype object of the new object. 'this.' properties are checked for before the prototype object so these will override. Or you could just attach properties after the object is built.

Option 1: Making a new event with modification
You could make an Object.assign-alike using proxies and construct a new event without modifying the original event
Example:
function EventModifier (evt, obj) {
const proxy = new Proxy(evt, {
get: (target, prop) => obj[prop] || target[prop]
})
return new evt.constructor(evt.type, proxy)
}
onclick = evt => {
evt = new EventModifier(evt, { altKey: true })
// Dispatch the new event on something else
console.log('clicked with alt key:', evt.altKey) // always true
}
This way you will use the same options as the original event that includes bubble, cancelable, key modifier, etc (doe it don't include any target as you are meant to dispatch the modified event on something else)
Option 2: Define new properties
Keeping the original event but override a key using Object.defineProperty you could use defineProperties if you want to add more than just one.
onclick = evt => {
Object.defineProperty(evt, 'target', { value: document.querySelector('script') })
console.log('target:', evt.target)
}

Inspired by Kofifus answer I just do that
function cloneEvent(type, event) {
var evt = new Event(type);
return Object.setPrototypeOf(evt,event);
}
or just
function cloneEvent(event){
return Object.create(event)
}
it returns a new object that protects you from edit the original object. but let you read and edit the property. thanks to js prototype chain
Newest js engine 🚂 support 'structuredClone' that should do it for you

Related

How to create shorthand for anyElement.querySelector method

One can create shorthand for document.querySelector with
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
so now let a = $('a') and let a = document.querySelector('a') are equivalent.
Is there a way to create shorthand for the querySelector method itself?
I.e. to make let a = element.shortHand(args) and let a = element.querySelector(args) to be equivalent for any (unknown in advance) element.
Edit: Since people are telling that doing the above is a bad idea, there is another question: How to make$ $$ selectors like the one in the Chrome DevTools, which accept the root element as second parameter?
I.e. to make let a = $('a',element) and let a = element.querySelector('a') to be equivalent.
Here are some options:
Add Method to Element.prototype
Element.prototype.shortHand = Element.prototype.querySelector
This "monkey-patches" the Element class in the DOM itself and adds this function on all elements in DOM, which is just a copy of the querySelector function.
This is very discouraged. It's bad for performance and it is bad in case browsers decide to add more functions in the future that conflicts with your function. But if you're just playing around and not shipping this code it should be fine.
Mini jQuery
If you're looking to create your own mini jQuery, you can also do something like this:
class MiniJQuery {
constructor(el) {
this.el = el;
}
shortHand(...query) {
return this.el.querySelector(...query);
}
// ... put any other functions you want to use
}
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
return new MiniJQuery(queryOrElement);
}
// Now you can:
const a = $(element).shortHand(args);
// which is equivalent to
const a = element.querySelector(args);
This is a much safer approach and not problematic. I don't think this adds much value as you can just type the slightly longer method name, but you could add more interesting methods on your class to make it worthwhile.
Proxy
Very similar to the approach above, but you can use a Proxy instead of the MinijQuery class to "forward" unknown methods to the element itself. This means that $(element) will have all the methods that element itself has.
Example:
const handler = {
get: function (target, prop, receiver) {
if (prop === "shortHand") {
return target.querySelector.bind(target);
}
const retVal = Reflect.get(...arguments);
// Bind methods to the element.
return typeof retVal === 'function'
? retVal.bind(target)
: retVal;
},
};
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
// You can add all sorts of custom function handlers here.
return new Proxy(queryOrElement, handler);
}
$('div') // gets divs
$(element).shortHand(...)
// works the same as element.querySelector
// But the HTMLElement methods still work too:
$(element).querySelector
$(element).querySelectorAll
$(element).className
// ...
Read More Here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

How to make a jQuery like select and action system(JavaScript)?

This is not for use in my project, Only for learning purposes.
In jQuery,
When we call $('h1'). it simply returns all the h1 elements from the document. Again when we make some action on an element like $('h1').hide(), it simply hides all the elements(cool ah?)
I want to learn this similar functionality, for example:
function app(elm){
const x = (typeof elm !== 'object') ? document.querySelectorAll(elm) : elm
return {
hide : function(){
x.forEach( target =>{
target.style.display = 'none';
});
}
}
}
This is a simple code here. So, If I call it like app('h1').hide(); it will hide all the h1 elements from the document. But if I call it like app('h1') it returns the object what I return that's normal.
In here I need all h1 elements from the document like jQuery. I mean It should work like this,
$('h1') === app('h1') //JQuery is equal to myCFunction (problem)
$('h1').hide === app('h1').hide() //jQuery is equal to myCFunction (solved)
[NOTE] Here is an article that is similar to my question but it's not my question answer.
Article Link
You can return x instead of a custom object, but before returning inject the hide function into x object's prototype like x.prototype.hide = function(){/*...*/}.
I think $("h1") does not return selected elements. It stores the selected elements. Instead we can have new function(getElement) to get select elements.Hope this code helps.
var App = function() {
var x ;
this.app = function (elem) {
x = document.querySelectorAll(elem);
return this;
}
this.hide = function(){
x.forEach(target => {
target.style.display = 'none';
});
return;
}
this.getElement = function(){
return x;
}
}
var $ = new App();
$.app("h1").hide();
console.log($.app("h1").getElement());
I've got a mostly working solution, but you still have to fix one small but annoying problem (see caveat 3). It's mostly done so I'll put it here anyway.
I think this is what you are looking for:
function app(selector) {
const retArr = document.querySelectorAll(selector); // The array to return
// Add proxies for all prototype methods of all elements
for (let e of retArr) {
let methods = getProtoMethods(e);
for (let mKey in methods) {
// Skip if the proxy method already exists in retArr
if (retArr[mKey] !== undefined) continue;
// Otherwise set proxy method
Object.defineProperty(retArr, mKey, {
value: function(...args) {
// Loop through all elements in selection
retArr.forEach(el => {
// Call method if it exists
if (el[mKey] !== undefined) el[mKey](...args);
});
}
});
}
}
return retArr;
// Gets all prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
return methods;
}
}
The idea is to put all the selected objects in an array, then define additional methods on the array. It should have all the method names of the selected objects, but those methods are actually proxies of those original methods. When one of these proxy methods is called, it calls the original method on all (see caveat 1) the selected objects in the array. But otherwise the returned object can just be used as a normal array (or more accurately, NodeList in this case).
However it's worth mentioning that there are several caveats with this particular implementation.
The list of proxy methods created is the union of the methods of all selected objects, not intersection. Suppose you selected two elements - A and B. A has method doA() and B has method doB(). Then the array returned by app() will have both doA() and doB() proxy methods. However when you call doA() for example, only A.doA() will be called because obviously B does not have a doA() method.
If the selected objects do not have the same definition for the same method name, the proxy method will use their individual definitions. This is usually desired behaviour in polymorphism but still it's something to bear in mind.
This implementation does not traverse the prototype chain, which is actually a major problem. It only looks at the prototypes of the selected elements, but not the prototypes of prototypes. Therefore this implementation does not work well with any inheritance. I did try to get this to work by making getProtoMethods() recursive, and it does work with normal JS objects, but doing that with DOM elements throws weird errors (TypeError: Illegal Invocation) (see here). If you can somehow fix this problem then this would be a fully working solution.
This is the problematic recursive code:
// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
// obj[pKey] throws error when obj is already a prototype object
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
// If obj's prototype has its own prototype then recurse.
if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
return methods;
} else {
return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
}
}
Sorry I cannot solve your problem 100%, but hopefully this at least somewhat helpful.

How to create shorter equivalent for addEventListener

I want to create a short function for addEvenTListener like jQuery does. I have seperate functions for select and event listener. But I want to be able to use $(selector).on instead of on(event, elem, callback, capture). How can I achieve that?
function $ (selector, scope) {
scope = scope ? scope : document;
return scope.querySelector(selector);
};
var on = function (event, elem, callback, capture) {
if (typeof (elem) === 'function') {
capture = callback;
callback = elem;
elem = window;
}
capture = capture ? true : false;
elem = typeof elem === 'string' ? document.querySelector(elem) : elem;
if (!elem) return;
elem.addEventListener(event, callback, capture);
};
JQuery returns an array of elements wrapped in jQuery object that provides useful methods in its prototype. So, simplified, it works similar to the code below. The last line is the way you want to attach event listeners to elements (if I understand correctly).
function $(selector, scope){
let z = Object.create($.prototype);
z.e = (scope || document).querySelector(selector);
return z;
}
$.prototype.on = function(event, callback, capture){
return this.e.addEventListener(event, callback, capture);
}
$("button").on("click", ()=>console.log("Clicked!"));
<button>Click</button>
jQuery returns a wrapper object which exposes the methods .on(), .attr(), .prop() etc, instead of the actual HTMLElement instance. So, one way you can accomplish that is to do the same.
Another, technically feasible way is to amend the prototype of the HTMLElement class:
HTMLElement.prototype.on = function(event, callback, capture) { ... };
Note, however, that manipulating prototypes you didn't create yourself is very much not recommended. It can easily break predefined behavior, perhaps not now but in future versions of JavaScript.
Node.prototype.listen = Node.prototype.addEventListener;
creates an alias or new reference.
// example
el.listen('click', function(){
// etc.
})

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

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