I have a problem removing event listener from a custom created element. The code below runs but I still get more event listeners (showed on screens below in chrome devtools).
stopPropagation(event) {
event.stopPropagation();
},
saveFileDocument() {
let elementToDownload = document.createElement("a");
// Some code to add attributes and stuff
// like "download" (i'm not sure it this is relevant)
document.body.appendChild(elementToDownload);
elementToDownload.addEventListener("click", this.stopPropagation);
elementToDownload.click();
elementToDownload.removeEventListener("click", this.stopPropagation);
document.body.removeChild(elementToDownload);
elementToDownload = null;
}
Screen from devtools (before and after click):
Before (549 listeners)
After (551 listeners)
The funny thing: when I remove the line with removing event listener the listnener count only increases by 1 on each function call.
Frontend framework I work on is Vue but I don't think it's Vue related.
This is my first question so sorry if I didn't go in too many details. I can add them when requested.
Thanks,
Paweł
Related
I am working with a very large application with a lot of JavaScript. I am trying to determine how I can find the location where a click event is being removed from a specific element.
There is a simple event listener on a specific field added via jQuery (this is an example).
$(document).ready(function() {
$('#id-name').on('click', function(e) {
window.alert('hello');
});
});
If break execution right after this and examine the element in Chrome Inspector, I see the event attached to the element. However, once I continue execution and the page finishes loading, the element no longer has the event. Something is removing it, but I can't find out where this is happening.
Is there a way to listen for "event removal" and trigger code then, so that I can identify where and how this is getting removed? Any other suggestions in locating where the click event is being removed?
Maybe you could override the removeEventListener method and trace it back to see what's calling it.
window.removeEventListener = (type, listener, useCapture)=>console.trace(type, listener, useCapture)
I'm working on a project using TypeScript and I'm facing an issue on elements having duplicate addEventListener to it.
The way the application works is that when the page first loads, nothing gets executed. All the TS classes keep alive waiting for an event to be dispatched. This event comes from the backend that will tell the application that it's ready to start.
const dbEvent = new CustomEvent("backendLoadEvent");
document.dispatchEvent(dbEvent);
When the TS class receives this event, it starts itself going through the dom getting the elements and applying all the eventListeners necessary for it to work properly.
Inside the TS file I will add the listener just as usual:
const btnOrder = document.getElementById('btn-order');
btnOrder.addEventListener('click', function (e) {
// Business logic here
console.log('debug button order click');
});
The problem is that the backend gets fired multiple times which is causing the button clicked to be executed multiple times as well. For example, sometimes that console.log inside the event click gets triggered twice simultaneously.
I tried to do something like this:
const btnOrder = document.getElementById('btn-order');
if (btnOrder.getAttribute('listener') !== 'true') {
btnOrder.addEventListener('click', function (e) {
const elementClicked = e.target;
elementClicked.setAttribute('listener', 'true');
});
}
The problem with this approach is that it relies on the click, so if the user clicks after the second event from the database, it's not going to work. I don't know when the event will arrive at the application nor when the user will click on the button.
Is there another approach I could use to prevent this behavior?
Note this is a simple example, but in some elements of the page there are a lot of events, this is a dashboard project. So we have like open/close modals, order and filter tables, add, remove and edit items on the table, and so on...
So I came across this weird issue and I don't know if I'm blatantly missing something. Visit this page (or any medium article). Open console and inject the following JS code.
for (const elem of document.querySelectorAll('.progressiveMedia-image.js-progressiveMedia-image')) {
elem.addEventListener('click', function () {
console.log(event.target);
});
}
Now click on the big images, expected behaviour should be (please correct me cause I seem to be wrong) that the target element is printed when you click on it the first time and also the second time. But as it turns out when you click on the image (zoomed) the second time (to zoom out) it doesn't print the target in the console.
I thought that there might be some overlay element and hence I bind the event on body to capture all of the events using the following injected JS.
document.body.addEventListener('click', function () {
console.log(event.target);
}, true);
But even with that I only get one console print of the target.
One theory for delegation using body not working might be following -
The newly created element would not be in the body in its time of creation, it will be moved to its place in the DOM tree later on. And hence delegation is not able to find it when did via body but able to capture it via document.
After a bit more exploring and injecting the following JS (taken from here and I know break point can be added, I did do that earlier but to no end so resorted to this.)
var observer = new MutationObserver(function (mutationsList, observer) {
for (var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
});
observer.observe(document, {
attributes: true,
childList: true,
subtree: true
});
I don't see any element being added to the DOM on click (it is being added on load) so that theory might not be correct. So I guess now the real question is why Event Capturing through document is able to find the click event where else not from body. I don't think delegation works on initial DOM structure since it would break the purpose.
Also if it is a duplicate please let me know, since I don't really know what to exactly search for.
probably because there is something in front of the zoomed image that intercepts the click event in capture mode and stops propagation.
I've got success with this
document.addEventListener("click", function(e){ console.log(e.target); }, true);
The image (you are trying to target) is dynamically made. After you already clicked the image once you should be able to target it.
document.querySelectorAll('.progressiveMedia-image.js-progressiveMedia-image')
This queries the DOM for all elements that have both the class progressiveMedia-image and js-progressiveMedia-image. You iterate over the result and bind an event listener to the click event of each element.
When you do click on one of the images, the JavaScript that is already running in the page creates new elements and displays them. These new elements might have the same classes, but did not exist originally when you searched the DOM. As such, they do not have their click event bound.
I am trying to automate a website that was built using GWT. My automation uses jQuery to select an appropriate element and then call the jQuery click() function to trigger a click event.
However, the expected action doesn't take place. Clicking the element with the mouse brings up a dialog box, but using jQuery does nothing. If I use jQuery to add a new click handler, I see the new handler executed in both cases, but the original handler only in the "real" click case.
Stepping into the Javascript code, I see very complicated code dealing with stack depth, leading me to think doing this automation may not be directly possible.
Does anyone know of a way to programmatically fire an event on a GWT-generated element? Or should this be working normally, and this site uses uniquely complicated code?
Edit: The code I'm using is quite simple:
var searchButton = jQuery('div.GH1CUEEFLB.GH1CUEEMLB:first');
if (searchButton && searchButton.length > 0) {
searchButton.click();
}
Stepping through the code shows that it selects the correct element, and proceeds to call click(). The existing event handler for the widget, according to Chrome's debugger, is complicated. Stepping through the process leads to a rabbit hole that is quite difficult to follow:
function(){
var stackIndex, returnTemp;
$stack_0[stackIndex = ++$stackDepth_0] = null;
try {
returnTemp = entry0(($location_0[stackIndex] = '57' , jsFunction), this, arguments);
$stackDepth_0 = stackIndex - 1;
return returnTemp;
}
catch (e) {
throw $location_0[stackIndex] = '63' , e;
}
$stackDepth_0 = stackIndex - 1;
}
The solution in this case was to trigger the click event on one of the child elements within the div. The event handler was attached to a particular div surrounding all the components of the button (label, icon, borders, etc). Triggering the event on that parent element did nothing. However, if I instead selected one of the leaf nodes in that subtree (say, the label itself), then triggering the click event brought up the dialog box as desired.
I guess the event handler's code was actively determining the exact element that triggered the event, but was not expecting the parent div to be that trigger source.
var searchButton = jQuery('div.GH1CUEEJT:first');
The above selects the leaf node upon which to trigger the event, even though the parent node 'div.GH1CUEEFLB.GH1CUEEMLB' held the event handler.
My situation is that I am trying to trigger a single event using the jQuery .trigger() method. However the element I am triggering has multiple click event listeners.
Actually finding what these listeners are and what they trigger from the source code is probably not viable as its included in the sites main JS file and its all minified and pretty much unreadable.
At the moment I know that the element when clicked performs some kind of ajax call and loads more data into the DOM of the page (which is what i want to trigger), however it also displays an overlay (which is what I want to suppress temporarily).
As its just an overlay there are workaround I can make; using a display:none on it straight after click etc. However it would be much more elegant if i could somehow suppress all click events on this element except the desired event.
Any ideas if this is actually possible? And if so how I would go about it?
You need to register your own event at the top of the event chain. And cancel the event chain in your event. Here is a solution with writing a custom jquery extention.
$.fn.bindFirst = function (which, handler) {
var $elm = $(this);
$elm.unbind(which, handler);
$elm.bind(which, handler);
var events = $._data($elm[0]).events;
var registered = events[which];
registered.unshift(registered.pop());
events[which] = registered;
}
$("#elm").bindFirst("click", function(e) {
// edit: seems like preventing event does not work
// But your event triggers first anyway.
e.preventDefault();
e.stopPropagation();
});
Reference:
https://gist.github.com/infostreams/6540654
EDIT:
https://jsfiddle.net/8nb9obc0/2/
I made a jsFiddle and it seems like event preventing does not work in this example. There might be another solution.