I've got a bug that looks like it's caused by an Event handler being attached a click event:
mxpnl.track_links("#pagebody a", "Click.body");
I'd like to watch to see how that Element's event handler is added (and when)
I found it in the Chrome Debugger (Dev Tools) > Elements and choose Break on Attribute modifications. Debugger never Breaks.
I also selected it's Parent Div (that it is within) and set Debugger (right-click on Element) > break on subtree modifications. Again, it never breaks.
What am I doing wrong here?
Adding an event listener isn't an attribute change (often) - rather, generally it's a call to addEventListener or an on- assignment. So, listening to attribute changes won't work.
One option would be to monkeypatch addEventListener so that debugger runs when addEventListener is called with the matching arguments. For example:
// snippet might not actually enter the debugger due to embedded sandboxing issues
const nativeEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (this.matches('div') && args[0] === 'click') {
console.log('listener is being added to a div');
debugger;
}
nativeEventListener.apply(this, args);
}
// then, when an event listener is added, you'll be able to intercept the call and debug it:
document.querySelector('div').addEventListener('click', () => {
console.log('clicked');
});
<div>click me</div>
In very simple term if explain then event listeners is just a function which add in an array with reference of string such as "click" and "on" etc..
So when you say.....
function myClick1(){
console.log('myclick1 called');
}
document.querySelector('mydiv').addEventListener('click', myClick1);
function myClick2(){
console.log('myclick2 called');
}
document.querySelector('mydiv').addEventListener('click', myClick2);
it add the function myClick1 and myClick2 in event listener array for click and execute in sequence as it added.
AND YOU CAN USE PROTOTYPE OF EVENT TARGET TO MONKEY PATCH THE EVENT LISTENER ARRAY.
Related
I want to overwrite event handler for click event. This is the event handler I have attached initially.
document.querySelector("[data-id='start-btn']")
.addEventListener("click", function (evt) {
//some code
});
Again after some condition, I want to over write this handler and attach new for 'click' event.
//removing
document.querySelector("[data-id='start-btn']")
.removeEventListener("click", function (evt) {
//some code
}, false);
//attaching new
document.querySelector("[data-id='start-btn']")
.addEventListener("click", function (evt) {
//code
});
But still it is executing the previous event handler.I have used removeEventListener(but I guess, its not working).
Guide me where I am going wrong.
The only way to remove a handler added with addEventListener is to use removeEventListener with exactly the same arguments. That means you'll need a reference to the original function:
var handler = function (evt) {
//some code
};
document.querySelector("[data-id='start-btn']").addEventListener("click", handler);
then to remove
document.querySelector("[data-id='start-btn']").removeEventListener("click", handler);
removeEventListener makes sense really only when using function references rather than passing an entire function body to both it and addEventListener, which would potentially mean mass duplication of code (and, as you've found, doesn't work anyway.)
So, prepare a reference to your function:
function my_func() { /* code */ }
And pass it as the handler argument to add/removeEventListener
document.querySelector('query').addEventListener('click', my_func);
document.querySelector('query').removeEventListener('click', my_func);
There is an easier way that utilises an older coding standard. If you specifically want only one event handler for a given type and element, you can use the DOM-zero onclick.
document.querySelector('query').onClick = my_func;
document.querySelector('query').onClick = my_func2; /* my_func() will no longer fire */
As you can see from some other answers, removing an event listener can be kind of a nightmare. Thankfully in certain circumstances there is an easier way: add another event listener that fires earlier and cancels out the rest.
In my case there was a click event handler I wanted to override and I was able to add another event handler with useCapture=true (among other things) to override it.
document.body.addEventListener('click',function (e) {
if (e.target.innerHTML.toLowerCase() == 'regular') {
e.target.insertAdjacentHTML('afterend','<div>One we do <strong>not want</strong></div>');
e.preventDefault();
}
});
document.body.addEventListener('click',function (e) {
if (e.target.innerHTML.toLowerCase() == 'fixed') {//or any event or other pre-/evaluations/conditions here
e.target.insertAdjacentHTML('afterend','<div>One we <strong>do want</strong></div>');
e.preventDefault();
e.stopPropagation();
}
//document.querySelector('.somethingelse').click();//etc.
},true);
<div>Regular</div>
<div>Fixed</div>
I was building a chrome extension and for some reason removeEventListener was not working as expected. The solution I came up with was to use the cloneNode method.
The documentation says
Cloning a node copies all of its attributes and their values,
including intrinsic (inline) listeners. It does not copy event
listeners added using addEventListener() or those assigned to element
properties.
What I did was created a clone for my actual element and replaced it with the cloned one. This removes all event listeners from the element. A simple example will be like
let newClonedElem = myActualElem.cloneNode(true);
myActualElem.parentNode.replaceChild(newClonedElem, myActualElem);
I wonder how can I get the list of event listeners for some event?
Suppose I add the event listener to window with this code:
window.addEventListener('click',
function() {
console.log(111);
// a lot of code ...
}, false)
But if I am about to see these listeners with window.onclick it returns null.
However when I click on the window this event handler fires. Can I see this list with another ways?
elem.onclick property and elem.addEventListener('click') do not depend on each other, but the order of attaching listeners really matters
<body>
<span> body stuff </span>
<script>
var element = document.body
element.addEventListener('click', function(){ console.log(1) })
element.onclick = function(){ console.log(2) }
element.addEventListener('click', function(){ console.log(3) })
</script>
</body>
do click on body and get '1 2 3'
then set element.onclick = null
do click on body and get '1 3'
2.1. the property onclick (which can be assigned to only one listener that is a function, not an array) is available from the script for each element (like any other property)
2.2. There is definetely no way to see the event listeners added with addEventListener from the script, however browser integrated debugger will show their state on any breakpoint (or in any given moment).
In Chrome 32+:
F12 -> Pick 'Elements' (Tab) -> Choose <body> element -> On the right pick 'EventListeners'
In other browsers you may need an extension (Dragonfly, Firebug etc.)
2.3. Chrome debugger will show all eventListeners (sorted by event name) for current element and its parents up to the document. But I do not know if it is possible to see the window event listeners.
If you use jQuery, there is a way to get from script a bunch of listeners added only by jQuery, though it's a different story.
I am working with an existing web app, in the app there are a variety of submit buttons on different forms, some using regular http post, some defining an onClick function, and some binding a js event handler to the button using a class on the element.
What I want to do, is bind another event handler to these buttons by just adding a class to the buttons, but what I want to determine is will the new event handler be guaranteed to be executed, or could one of the form submit actions happen before it does meaning my new function isn't hit.
The example scenario is I want to add a class to these buttons that bimds them all to a common js function that simply logs usage to some api. Is there a risk that the logging function isn't called because the form submit has navigated away from the page?
I've not done loads of js development, and I could test this 100 times over and just get lucky with it firing.
Below is some code I have tested with for one of the examples - again, I'm not asking how to bind multiple events, the question is to about my understanding of the spec and whether execution of all handlers is guaranteed.
$(document).ready(function(){
$('.testingBtn').click(function() {
window.location.replace("http://stackoverflow.com");
});
$( ".testingBtn" ).click(function(){
alert('submitting!');
});
});
<input class="testingBtn" type="submit" id="submitform" value="Complete Signup" />
As seen above, I can bind the multiple events, and in this example, just directed to another url, but this could be a form.submit() etc. In my testing the alert has always fired first, but am I just getting lucky with the race conditions?
In JS, you don't really have control over what order the event handlers are called, but with careful delegation and well-placed listeners, it is possible.
Delegation is one of the most powerful features of the event model. As you may or may not know: in JS, an event is handed to the top of the dom, from where it propagates down to the element onto which the event should be applied. It stands to reason, therefore, that an event listener attached to the global object will call its handler prior to a listener that has been attached to the element itself.
window.addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
console.log('window noticed you clicked something');
console.log(target);//<-- this is the element that was clicked
}, false);//<-- we'll get to the false in a minute
It's important to note we actually have access to the event object in the handlers. In this case, we left the event object untouched, so it'll just continue to propagate down to the target, on its way down, it might meet with something like this:
document.getElementById('container').addEventListener('click', function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (target.tagName.toLowerCase() !== 'a' || target.className.match(/\bclickable\b/))
{
return e;//<return the event, unharmed
}
e.returnValue = false;
if (e.preventDefault)
{
e.preventDefault();
}
}, false);
Now, this handler will be called after the listener at the window level calls its helper. This time, the event is changed if the clicked element didn't have the clickable class, or the element is a link. The event is canceled, but it lives on, still. The event is still free to propagate further down the dom, so we might encounter something like:
document.getElmentById('form3').addEventListener('click',function(e)
{
e = e || window.event;
if (e.returnValue === false || e.isDefaultPrevented)
{//this event has been changed already
//do stuff, like validation or something, then you could:
e.cancelBubble = true;
if (e.stopPropagation)
{
e.stopPropagation();
}
}
}, false);
Here, by calling stopPropagation, the event is killed off. It can't propagate further down the dom to its target unless the event was already altered. If not, the event object travels further down the DOM, as if nothing happened.
Once it reaches its target node, the event enters its second phase: the bubble phase. Instead of propagating down into the deeps of the DOM, it climbs back up, to the top level (all the way to the global object, where it was dispatched... from whence it came and all that).
In the bubble phase, all the same rules apply as in the propagation phase, only the other way around. The event object will encounter the elements that are closest to the target element first, and the global object last.
There's a lot of handy, and clear diagrams for this here. I can't put it any better than good 'ol quirksmode, so I suggest you read what they have to say there.
Bottom line: when dealing with 2 event listeners, attach them both on a different level to sort-of queue them the way you like.
If you want to guarantee both are called, only stop the event from propagating in that handler that will be called last.
When you've got two listeners, attached to the same element/object for the same event, I've never come across a situation where the listener that was attached first, wasn't also called first.
That's it, I'm off to bed, hoping I made sense
jQuery makes this easy.
$(document).on('click', '.someclass', function() {
doStuff();
});
$(document).on('click', '.someclass', function() {
doMoreStuff();
});
Handlers then both will fire on click. jQuery keeps a queue of handers for you. And handles document clicks that match a selector of your choice so that they can be triggered no matter when your buttons are created.
I am/was having a similar issue as this. However I can not affect the order of/delegate the pre-existing 'click' events (added by Wicket framework).
But I still need to execute a new custom event before any of the 'click' or 'change' events handled by the framework.
Luckily there are several events that are actually executed in order. The 'mousedown' and the 'mouseup' happens to happen before the 'click'.
http://en.wikipedia.org/wiki/DOM_events
$(document).on('mousedown', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
console.log(target + ' before default event'); // Hold mouse button down to see this message in console before any action is executed
});
OR
$(document).on('mouseup', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
alert(target + ' before default event'); // You may not notice this event fires when the page changes unless this is an alert
});
This will allow the logging to be done (e.g. via ajax) before the actual event is executed e.g. a page change via (ajax) link.
Of course you may need to have more sophisticated means to detect for what the additional event handling should be done, but you can use for example the 'target' information for this. => This script monitors everything on the page, as this is how I need this to be done.
I have a site that uses AJAX to navigate. I have two pages that I use a click and drag feature using:
$(".myDragArea").mousedown(function(){
do stuff...
mouseDrag = true; // mouseDrag is global.
});
$("body").mousemove(function(){
if (mouseDrag) {
do stuff...
}
});
$("body").mouseup(function(){
if (mouseDrag) {
do stuff...
mouseDrag = false;
}
});
I just type that out, so excuse any incidental syntax errors. Two parts of the site use almost identical code, with the only difference being what is inside the $("body").mouseup() function. However, if I access the first part, then navigate to the second part, the code that runs on mouseup doesn't change. I have stepped through the code with Firebug, and no errors or thrown when $("body").mouseup() is run when the second part loads.
So, why doesn't the event handler change when I run $("body").mouseup() the second time?
Using $("body").mouseup( ... ) will add an event handler for the body that is triggered at mouseup.
If you want to add another event handler that would conflict with current event handler(s) then you must first remove the current conflicting event handler(s).
You have 4 options to do this with .unbind(). I'll list them from the least precise to the most precise options:
Nuclear option - Remove all event handlers from the body
$("body").unbind();
This is pretty crude. Let's try to improve.
The elephant gun - Remove all mouseup event handlers from the body
$("body").unbind('mouseup');
This is a little better, but we can still be more precise.
The surgeon's scalpel - Remove one specific event handler from the body
$("body").unbind('mouseup', myMouseUpV1);
Of course for this version you must set a variable to your event handler. In your case this would look something like:
myMouseUpV1 = function(){
if (mouseDrag) {
do stuff...
mouseDrag = false;
}
}
$("body").mouseup(myMouseUpV1);
$("body").unbind('mouseup', myMouseUpV1);
$("body").mouseup(myMouseUpV2); // where you've defined V2 somewhere
Scalpel with anesthesia (ok, the analogy's wearing thin) - You can create namespaces for the event handlers you bind and unbind. You can use this technique to bind and unbind either anonymous functions or references to functions. For namespaces, you have to use the .bind() method directly instead of one of the shortcuts ( like .mouseover() ).
To create a namespace:
$("body").bind('mouseup.mySpace', function() { ... });
or
$("body").bind('mouseup.mySpace', myHandler);
Then to unbind either of the previous examples, you would use:
$("body").unbind('mouseup.mySpace');
You can unbind multiple namespaced handlers at once by chaining them:
$("body").unbind('mouseup.mySpace1.mySpace2.yourSpace');
Finally, you can unbind all event handlers in a namespace irrespective of the event type!
$("body").unbind('.mySpace')
You cannot do this with a simple reference to a handler. $("body").unbind(myHandler) will not work, since with a simple reference to a handler you must specify the event type ( $("body").unbind('mouseup', myHandler) )!
PS: You can also unbind an event from within itself using .unbind(event). This could be useful if you want to trigger an event handler only a limited number of times.
var timesClicked = 0;
$('input').bind('click', function(event) {
alert('Moar Cheezburgerz!');
timesClicked++;
if (timesClicked >= 2) {
$('input').unbind(event);
$('input').val("NO MOAR!");
}
});
Calling $("body").mouseup(function) will add an event handler.
You need to remove the existing handler by writing $("body").unbind('mouseup');.
jQUery doesn't "replace" event handlers when you wire up handlers.
If you're using Ajax to navigate, and not refreshing the overall DOM (i.e. not creating an entirely new body element on each request), then executing a new line like:
$("body").mouseup(function(){
is just going to add an additional handler. Your first handler will still exist.
You'll need to specifically remove any handlers by calling
$("body").unbind("mouseUp");
I have created a new YAHOO.util.KeyListener to attach to a specific element and have also created another new YAHOO.util.KeyListener to attach to the entire document. They are both associated with the enter key (keys:13).
In the handler function for the listener attached to the specific element, I have the following code:
getDetailsLocalnameInput = function(e) {
getDetails(localnameInput.value);
YAHOO.util.Event.preventDefault(e);
YAHOO.util.Event.stopPropagation(e);
};
Yet, the event from the keypress continues to propagate up to the key listener attached to the entire document. I do not want the handler for the key listener attached to the entire document to get kicked off. I am sure that both handlers are being called, but only want the handler attached to the specific element to run.
Is it correct to use YAHOO.util.Event.stopPropagation with YAHOO.util.KeyListener?
Is there a different way I should go about preventing the keypress event from being propagated?
I have also tried using the function YAHOO.util.Event.stopEvent and setting e.cancelBubble with no success.
I have been testing all of this with Firefox 3.5. I cannot get stopPropagation() to work at all.
Try this:
getDetailsLocalnameInput = function(e) {
getDetails(localnameInput.value);
if(window.event){
e.cancelBubble=true;//In IE
}else{
evt.stopPropagation();//in Others
}
//YAHOO.util.Event.preventDefault(e);
//YAHOO.util.Event.stopPropagation(e);
};