How to keep variables in JQuery to use later in an event? - javascript

I am trying to create a code to fill a form in which the options of some selectors depend on what is selected in a previous selector. I am using the jQuery library to create the events.
I would like to be able to make the event be able to observe the variables even when the code that creates the event has already finished.
Below is an example of what I need to do.
function preValForm() {
App.get('params').then((params) => {
let devices = []
params.devices.forEach(i => { // Here is the params Object { devices: [[...]] , stock: [[...]] }
if (i[0] == "Mobile" && devices.indexOf(i[1]) == -1) { // Not insert duplicates to the array
devices.push(i[1]) // Insert value to array if isn't duplicate
}
})
$('#listDevices').html(devices.map(d => {
return `<option value="${d}">${d}</option>`
}).join('')) // Insert option values to a select element in DOM
// Here is the problem ...
$('#listDevices').on('change', (ev) => { // Create the event when selector #listDevices changes
let stock = [] // Declare variable stock
params.stock.forEach(s => { // ! params is not defined **! -- This is the error**
if (s[1] == ev.target.value && stock.indexOf(s[1]) == -1) {
stock.push(s[1])
}
})
$('#stockOptions').html(stock.map(k => {
return `<option value="${k}">${k}</option>`
}).join(''))
})
})
}
I have seen that this can be done, for example with the SweetAlert2 library you see this behavior where you define an event called preConfirm and when executed the variables are still visible for that event. I would like to be able to do something similar to that behavior, that when I make a change in the selection the event is executed and that event recognizes my variable 'params'.
Thanks

Related

Petite-Vue filtered array reactivity?

I played arround with Petite-Vue and really like it. Works fine with reactivity / ui updates and helpers like "$el" and "$refs" for easy form field handling / data binding.
Now I tried to have a reactive list with updates live if backend data (gundb) changes.
UI updates work fine if I list all array entries with "v-for".
If I try to filter the array of players by one team (object property "team") the UI is initialy correct rendered, but if a players name changed the update is pushed to gundb and by listener also to players array based on PetiteVue.reative(). So all is fine, but no UI update is triggered. Item is a Proxy as needed for (petite-)vue reactivity.
So it looks like a players.filter(...) call break reactivity. Also tried a method and getter inside of a component and also directly by write players.filter(...) to the html template.
Do I missing the right way how to trigger a re-render of changed filtered reactive array of objects?
I have a function to sync gundb "backend" changes to Petite-Vue.reactive object
gunArray2vue = (gunNode, vueArray) => {
gunNode.map().on((value, soul) => {
item = vueArray.find(item => item && item._ && item._['#'] === soul)
console.log('gunArray2vue', `soul=${soul}`, value, item)
if(!!item && value === null) {
// item in VUE but seems deleted... remove from vue array...
console.log("ToDeleteItem", item)
vueArray.splice(vueArray.indexOf(item), 1)
} else if(!value) {
console.log("Ignore creation of empty object")
return // don't add empty object...
} else if(item) {
//vueArray[vueArray.indexOf(item)] = value // update
console.log("UpdateItem")
item = value // update
} else {
console.log("AddArrayItem")
vueArray.push(value) // add new
}
})
}
Changed Value checked with console:
checked change in js console
But UI isn't refreshed.
Here the code in componente
tested as getter / function
//filteredPlayers: function() {
get filteredPlayers() {
if(this.form.nr || this.form.name) {
return this.players.filter(player =>
player.name.includes(this.form.name) || player.nr == this.form.nr
)
} else {
return this.players
}
}
The filtering is reactive to my input field! But changed name is ignored.
And here is a simpler getter function
get myPlayers() {
page.players
}
But ui isn't updated if an item changes without refresh.
So filtering works reactive, but changed items are not detected and re-rendered.
Also tried directly to use the unfiltered PetiteVue.reactive object like that.
<div v-for="player in page.players">{{player.nr}} {{player.name}}</div>
Rendering works fine, but also not updated on change.
Update
Noticed during my tests today, that updates work if I directly change my original object page.players, but not / no more if I use a reference to the original reactive object?
So I can't use the reactive object by reference without loose ui reactivity for item changes?
Update2
Add and remove an reactive array entry works fine!
Just update an item won't work. Tested different ways to set the item object / key -> val of object. Change isn't reactive.
Function with my tests
gun2arrayListener = (name, table) => {
console.log(`Register Listener Gun table "${name}"`)
DB.get(name).map().on((value, soul) => {
console.log("DEBUG", soul, value)
item = table.find(item => item?._['#'] === soul)
index = table.indexOf(item)
console.log('gun2array', `soul=${soul}`, value, item)
if(Object.is(item, value)) {
console.log("Skip unchanged item")
} else if(item && value === null) {
console.log("ToDeleteItem", item)
table.splice(index, 1)
} else if(!value) {
console.log("Ignore creation of empty object?!")
} else if(item) {
console.log("UPDATE", "ARRAY", item, value)
// !!! CHANGES NOT REACTIVE HERE !!!
// !!! method is triggered, changes looks good, but reload page needed to update UI !!!
//item = value
//TABLES[name][index] = value
//Object.assign(item, value)
//table.splice(index, 1, value)
//table.splice(index, 1); table.push(value)
for (const [key, val] of Object.entries(value)) {
console.log(`${key}: ${val}`)
//item[key] = val
store.players[index][key] = val
console.log(item)
}
} else {
console.log("AddNewGunNodeItem", value)
table.push(value) // add new
}
})
}
Strange... if I test reactivity from outside it works ?!
store.players[0].name = "BUM!" // works as needed in function... ?!
I noticed gun fires the listener twice, but that should result in correct updated array object, just changed to the new value twice...
I can't prevent that, because can't compare old object (vue proxy object) with the new object (plain object without vue proxy) to skip unchanged object here. Object.is results in false because of the Proxy arround...

How to use a constant in map return function ReactJS

I am wondering how can I use a constant in the map function, basically meaning: I have saved correctly the option I want from my falling menu regarding the constant (I checked it with console.log), for instance I have a name chosen and then I want to use it in the map function but unfortunately I get all the elements undefined when I use the constant; when I replace the constant with a directly written "name", I get all the elements correctly with their names.
Filterhosts=() =>{
var newState = this.state.array.slice(); // in the state array is empty
const selectedOption = this.state.selectedOption;
const writtenOption = this.state.writtenOption;
console.log(selectedOption) //ok
const namearray= this.state.filteredhosts.map(host=> {
return (
host.software.map((sub, subindex) => {
if(selectedOption=="name" || selectedOption=="vendor") {
newState.push(sub.selectedOption) //when I write sub.selectedOption , I receive empty array with all elements as undefined otherwise I become the names of all elements
}
else {
if(sub.vulnerable==true){
newState.push(sub.vulnerability.cve)}
}
})
)
})
const filteredarray = newState.filter( function(item){
return item === writtenOption // here I become properly the searched name//vendor
}
// how to show the whole info for the searched name/vendor(cpe, cve, cvss etc.)
)
console.log(newState); //ok
console.log(filteredarray); //ok
}
Oh I see.
sub.name
is the same as
sub["name"]
which is also the same as
sub[selectedOption]
IF selectedOption is "name". So just use newState.push(sub[selectedOption]) and I think that should work for you.

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

DraftJS triggers content change on focus?

I have an editor on a page and save button. I want save button only to appear if there are changes (since last save). So, when you save, i set this.setState({hasChanges: false}) and in onChange function i set hasChanges to true.
And that's all working fine. The issue is that editor will fire onChange event when editor gain focus (but content is not changed yet). And, that is expected behaviour according to their documentation
The event listeners within the component will observe focus changes
and propagate them through onChange as expected, so state and DOM will
remain correctly in sync.
Is there some way to know if content has changed inside onChange method, or only selection is changed?
I posted this answer to your question on github earlier today, but I'll add it here as well for future reference:
You're right in that onChange is called every time the editorState changes. And since the editorState holds both the selectionState and the contentState, it will change both when there's a content edit, and when the selection/focus changes.
If you want to know if the change that triggered the onChange method was in the contentState or in the selectionState, you could compare them with their old values:
function onChange(newState) {
const currentContentState = this.state.editorState.getCurrentContent()
const newContentState = newState.getCurrentContent()
if (currentContentState !== newContentState) {
// There was a change in the content
} else {
// The change was triggered by a change in focus/selection
}
}
Now, if you know that the change was in the contentState, you can get some more info by calling newState.getLastChangeType(). It should return any of these values (unless you, or some plugin you've added, have created new change types).
However, sometimes just the conditional if (currentContentState !== newContentState) not work and not detect the change for cases like when you to modify the content state using Entity.mergeData together with forceSelection because this change is stored inside entity and is not exposed in contentState.
So you could do something like that additionaly.
this.currentEntityMap = null;
function onChange(newEditorState) {
const currentContentState = editorState.getCurrentContent();
const newContentState = newEditorState.getCurrentContent();
const newContentStateRaw = convertToRaw(newContentState);
const newEntityMap = newContentStateRaw ? newContentStateRaw.entityMap : null;
//so if you have the two entity maps, you can to compare it, here an example function.
const isEntityMapChanged = this.isEntityMapChanged(newEntityMap);
this.currentEntityMap = newEntityMap;
}
isEntityMapChanged(newEntityMap) {
let isChanged = false;
if (this.currentEntityMap) {
loop1:
for (const index of Object.keys(newEntityMap)) {
if (this.currentEntityMap[index] && newEntityMap[index].type === this.currentEntityMap[index].type) {
loop2:
for (const prop of Object.keys(newEntityMap[index].data)) {
if (newEntityMap[index].data[prop] !== this.currentEntityMap[index].data[prop]) {
isChanged = true;
break loop1;
}
}
} else {
isChanged = true;
break loop1;
}
}
}
return isChanged;
}
then to used the flag isEntityMapChanged and the first conditional for to do the save.
you should use code follow:
const onChange = newState => {
if (
// when content change or line style change will change the state
!newState.getCurrentContent().equals(editorState.getCurrentContent()) ||
!newState
.getCurrentInlineStyle()
.equals(editorState.getCurrentInlineStyle())
) {
setEditorState(newState);
}
};
if you just use content equal ,then line style change will not on effect.

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