When I was exploring StackOverflow's code, I get:
And I tried to get onclick event handler. There are the report:
> $0.onclick
< null
> $0.parentElement.onclick
< null
But $0.click() gives me some result (answer is upvoted).
How StackOverflow developers hid it and how to make it by hand, in pure JS?
While the .onclick property is one way to add a click event listener to elements, it's not the only way. Another way is via .addEventListener(). When an event listener is added to an element via .addEventListener(), the onclick attribute/property doesn't update to the event handler function. The .onclick property is only set on an element if you're using the onclick="" attribute or if the code has set the property elem.onclick = function() {...}, which nowdays isn't very often.
Chrome does provide you with a way to find the event handlers on different elements. When you're in your developer tools, you can select the element you're interested in, click "Event Listeners" in the right-hand pane, find the event you're intrested in, in this case that's the "click" event, and then look for your element:
Your element happens to have an event associated with it, but this might not always be the case. As the comments on your question have pointed out, you can sometimes run into cases where there is no click event handler on your specific element, but rather, it is added to a parent of that element. In this case, when you click on your element, the event propegates/"bubbles" up through the DOM, eventually reaching your parent element with the click event listener. The event handler on the parent can then see what element was originally clicked, and can then perform some actions based on that. This is known as event delegation, and is one of the reasons why you might not always find an event tied to your element even when looking in dev tools.
Related
I bind two event handlers on this link:
<a href='#' id='elm'>Show Alert</a>
JavaScript:
$(function()
{
$('#elm').click(_f);
$('#elm').mouseover(_m);
});
function _f(){alert('clicked');}
function _m(){alert('mouse over');}
Is there any way to get a list of all events bound on an element, in this case on element with id="elm"?
In modern versions of jQuery, you would use the $._data method to find any events attached by jQuery to the element in question. Note, this is an internal-use only method:
// Bind up a couple of event handlers
$("#foo").on({
click: function(){ alert("Hello") },
mouseout: function(){ alert("World") }
});
// Lookup events for this particular Element
$._data( $("#foo")[0], "events" );
The result from $._data will be an object that contains both of the events we set (pictured below with the mouseout property expanded):
Then in Chrome, you may right click the handler function and click "view function definition" to show you the exact spot where it is defined in your code.
General case:
Hit F12 to open Dev Tools
Click the Sources tab
On right-hand side, scroll down to Event Listener Breakpoints, and expand tree
Click on the events you want to listen for.
Interact with the target element, if they fire you will get a break point in the debugger
Similarly, you can:
right click on the target element -> select "Inspect element"
Scroll down on the right side of the dev frame, at the bottom is 'event listeners'.
Expand the tree to see what events are attached to the element. Not sure if this works for events that are handled through bubbling (I'm guessing not)
I'm adding this for posterity; There's an easier way that doesn't involve writing more JS. Using the amazing firebug addon for firefox,
Right click on the element and select 'Inspect element with Firebug'
In the sidebar panels (shown in the screenshot), navigate to the events tab using the tiny > arrow
The events tab shows the events and corresponding functions for each event
The text next to it shows the function location
You can now simply get a list of event listeners bound to an object by using the javascript function getEventListeners().
For example type the following in the dev tools console:
// Get all event listners bound to the document object
getEventListeners(document);
The jQuery Audit plugin plugin should let you do this through the normal Chrome Dev Tools. It's not perfect, but it should let you see the actual handler bound to the element/event and not just the generic jQuery handler.
While this isn't exactly specific to jQuery selectors/objects, in FireFox Quantum 58.x, you can find event handlers on an element using the Dev tools:
Right-click the element
In the context menu, Click 'Inspect Element'
If there is an 'ev' icon next to the element (yellow box), click on 'ev' icon
Displays all events for that element and event handler
Note that events may be attached to the document itself rather than the element in question. In that case, you'll want to use:
$._data( $(document)[0], "events" );
And find the event with the correct selector:
And then look at the handler > [[FunctionLocation]]
I used something like this if($._data($("a.wine-item-link")[0]).events == null) { ... do something, pretty much bind their event handlers again } to check if my element is bound to any event. It will still say undefined (null) if you have unattached all your event handlers from that element. That is the reason why I am evaluating this in an if expression.
When I pass a little complex DOM query to $._data like this: $._data($('#outerWrap .innerWrap ul li:last a'), 'events') it throws undefined in the browser console.
So I had to use $._data on the parent div: $._data($('#outerWrap')[0], 'events') to see the events for the a tags. Here is a JSFiddle for the same: http://jsfiddle.net/giri_jeedigunta/MLcpT/4/
NOTICE: The cause of the problem has been found, read the comments to the first answer.
I have a dropdown list of things, that is hidden until the user invokes it.
It's something like this:
<div>
<button></button>
<ul>
<li></li>
....
<li></li>
</ul>
</div>
The basic idea:
The list becomes visible when the user presses the button shown in the code above.
I need to make the list able to be navigated by keyboard,
i.e. if the user presses up or down while the list is open, the appropriate li will be selected (as if the mouse was hovering over it instead)
The event listener responsible for giving this functionality to the list should be attached when the list becomes visible and be removed when the list becomes hidden again.
Something like what Bitbucket does for the dropdown lists, but even simpler.
The issue:
I tried to attach an event listener to the ul and then on the div element, when the former had no effect, to no avail.
The code is this
ON SHOW
this.<ul or div element here>.addEventListener('keydown', this.keyboardNavigation.bind(this));
ON HIDE
this.<ul or div element here>.removeEventListener('keydown', this.keyboardNavigation.bind(this));
and the callback is like so
function keyboardNavigation(e) {
console.log('foo');
}
NOTE: "this" is an object to which the div and the ul are both properties of, and the callback function is actually a method of that object.
QUESTION 1:
Why is the keydown event not working when I attach it to either the ul itself or the parent div?
Anyway, since these did not work, I decided to attach the listener to the document.
ON SHOW
document.addEventListener('keydown', this.keyboardNavigation.bind(this));
ON HIDE
document.removeEventListener('keydown', this.keyboardNavigation.bind(this));
Same callback.
Now, while this works, I noticed that the event listener is not removed from the document.
I later noticed that another keydown event listener I had attached to the document for another task, is also not removed when that task is done, while it should.
QUESTION 2:
Why are the event listeners not removed? I cannot understand what I am doing wrong, I am removing the exact same callback on the exact same event as were those that were added.
Any help will be much appreciated.
NOTE:
I have tried doing it with jQuery's .on() and .off() instead, as suggested here, although I do not want to use jQuery, yet same thing is happening.
My thoughts:
1 Is it because the DIV or UL isn't getting the keyboard events because the don't have focus? Whereas the document is always getting the bubbled events?
To test this, click in the DIV/UL and type and see if the keyboard events get triggered then.
I think binding to the document - if you want the user to be able to just start typing after clicking - is the right thing to do here.
2 Is this because you are not removing the same handler you created? You should retain a reference to the handler you create with the first bind call and pass this reference in to the remove call - otherwise you're creating another (different) handler and asking to remove that.
E.g.:
var f = this.keyboardNavigation.bind(this);
document.addEventListener('keydown', f);
document.removeEventListener('keydown', f);
event.preventDefault() will override default event behavior of an element. How can I temporarily override all click bindings and not just default ones?
Or is there a way to save all the click bindings so I can unbind them and use them later?
Well this is not a proper answer but a workaround. We can push the required handler on top of the stack and then used return false to stop other bindings. https://github.com/private-face/jquery.bind-first
You can use jQuery.clone(true) what this does is return data for an element. The parameter that is set to true means to also copy over all the events as well.
So if you clone the element into a variable you can bring back the old click events by simply replacing your target element with its older clone (which has the old events)
So it goes as follows:
step 1:
clone the target element using jQuery.clone(true) into a variable
step 2:
remove all click events from the target element using jQuery.off('click')
step 3:
bind your event to the target element with jQuery.on('click' function etc...)
step 4:
when you're done replace the target element with its clone (which has the old events)
Here is a JSFiddle for your viewing pleasure
(Sorry for the simpleness of the JSFiddle I mocked it up quickly and I have no example situation where I would use this.)
EDIT: I forgot to explain jQuery.clone(true)
You may catch the click before it can bubble by using
element.addEventListener(type, listener[, useCapture]);
This way you can 'catch' the click before triggering the jQuery click handler, like this (which I took from this stackoverflow question:
document.addEventListener('click', function(e) {
e.stopPropagation();
}, true);
For more information (and some IE < 9 support), see developer.mozilla
Edit: details about useCapture from Mozilla:
If true, useCapture indicates that the user wishes to initiate capture. After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events which are bubbling upward through the tree will not trigger a listener designated to use capture. See DOM Level 3 Events for a detailed explanation. If not specified, useCapture defaults to false.
If you have control over all of the JS code and can bind your own handler first and all other event handlers are bound with jQuery then you can do this:
var overrideClick = false;
$("#yourElementId").click(function(e) {
if (overrideClick) {
e.stopImmediatePropagation();
// e.preventDefault(); uncomment this if you want to prevent default action too
}
});
Where some other part of your code would set overrideClick = true when needed.
Demo: http://jsfiddle.net/NCa5X/
jQuery calls handlers in the order they are bound, so you can then use event.stopImmediatePropagation() to prevent the other handlers from being called.
I have HTML similar to the following in my page
<div id="someDiv">
<img src="foo.gif" class="someImg" />
</div>
The wrapper div is set up such that when it is clicked, it's background-color changes using the following jQuery code.
$("div").click(function(event){
$(this).css("background-color", "blue");
});
I also have some jQuery associated with my img that will do some other function (for the sake of argument I am going to display and alert box) like so:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
});
The issue I have come across is that when I click on the img, the event associated with the div is also triggered. I'm pretty sure that this is due to the way that jQuery (or indeed JavaScript) is handling the two DOM elements - clicking the img would require you to also technically click the div, thus triggering both events.
Two questions then really:
Is my understanding of the
DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
Are there any jQuery methods that
would allow me to perform actions on
a child element without invoking
those associated with its parent?
That is known as event bubbling, you can prevent it with stopPropagation():
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
.
Is my understanding of the DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
That is because of what is known event bubbling.
Are there any jQuery methods that would allow me to perform actions
on a child element without invoking
those associated with its parent?
Yes, you need stopPropagation()
No, this is by design. Events bubble up through the entire dom, if you put another handler on body, it would fire too
Yes :) JQuery normalizes the event object, so adding event.stopPropagation() in your img click handler will give you the behavior you expect on all browsers
The problem you just facing is called "event bubbling". That means, if you click on a nested
element, that click event will "bubble up" the DOM tree.
If other elements also are bound to an click event, their listeners will fire aswell.
Solution to prevent this is called:
stopPropagation()
which is used within your event handler
$("img[class=someImg]").click(function(event){
event.stopPropagation();
alert("Image clicked");
});
This is what's called event bubbling, and you can stop it to get the behavior you want with .stopPropagation() (or return false; if you want to stop the event completely, including handlers on the same level), like this:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
You can view a demo here, comment it out and click run again to see the difference.
The short version is that when most event types happen, they happen on the immediate element, then bubble up the DOM, occurring on each parent as they go. This is not jQuery specific at all, native JavaScript does this. If you're more curious, I'd read the linked article, it has a great explanation of what's going on.
I bind two event handlers on this link:
<a href='#' id='elm'>Show Alert</a>
JavaScript:
$(function()
{
$('#elm').click(_f);
$('#elm').mouseover(_m);
});
function _f(){alert('clicked');}
function _m(){alert('mouse over');}
Is there any way to get a list of all events bound on an element, in this case on element with id="elm"?
In modern versions of jQuery, you would use the $._data method to find any events attached by jQuery to the element in question. Note, this is an internal-use only method:
// Bind up a couple of event handlers
$("#foo").on({
click: function(){ alert("Hello") },
mouseout: function(){ alert("World") }
});
// Lookup events for this particular Element
$._data( $("#foo")[0], "events" );
The result from $._data will be an object that contains both of the events we set (pictured below with the mouseout property expanded):
Then in Chrome, you may right click the handler function and click "view function definition" to show you the exact spot where it is defined in your code.
General case:
Hit F12 to open Dev Tools
Click the Sources tab
On right-hand side, scroll down to Event Listener Breakpoints, and expand tree
Click on the events you want to listen for.
Interact with the target element, if they fire you will get a break point in the debugger
Similarly, you can:
right click on the target element -> select "Inspect element"
Scroll down on the right side of the dev frame, at the bottom is 'event listeners'.
Expand the tree to see what events are attached to the element. Not sure if this works for events that are handled through bubbling (I'm guessing not)
I'm adding this for posterity; There's an easier way that doesn't involve writing more JS. Using the amazing firebug addon for firefox,
Right click on the element and select 'Inspect element with Firebug'
In the sidebar panels (shown in the screenshot), navigate to the events tab using the tiny > arrow
The events tab shows the events and corresponding functions for each event
The text next to it shows the function location
You can now simply get a list of event listeners bound to an object by using the javascript function getEventListeners().
For example type the following in the dev tools console:
// Get all event listners bound to the document object
getEventListeners(document);
The jQuery Audit plugin plugin should let you do this through the normal Chrome Dev Tools. It's not perfect, but it should let you see the actual handler bound to the element/event and not just the generic jQuery handler.
While this isn't exactly specific to jQuery selectors/objects, in FireFox Quantum 58.x, you can find event handlers on an element using the Dev tools:
Right-click the element
In the context menu, Click 'Inspect Element'
If there is an 'ev' icon next to the element (yellow box), click on 'ev' icon
Displays all events for that element and event handler
Note that events may be attached to the document itself rather than the element in question. In that case, you'll want to use:
$._data( $(document)[0], "events" );
And find the event with the correct selector:
And then look at the handler > [[FunctionLocation]]
I used something like this if($._data($("a.wine-item-link")[0]).events == null) { ... do something, pretty much bind their event handlers again } to check if my element is bound to any event. It will still say undefined (null) if you have unattached all your event handlers from that element. That is the reason why I am evaluating this in an if expression.
When I pass a little complex DOM query to $._data like this: $._data($('#outerWrap .innerWrap ul li:last a'), 'events') it throws undefined in the browser console.
So I had to use $._data on the parent div: $._data($('#outerWrap')[0], 'events') to see the events for the a tags. Here is a JSFiddle for the same: http://jsfiddle.net/giri_jeedigunta/MLcpT/4/