Here's a question about binding click events with jQuery that I'm trying to make sense of.
Say I have a block element I bind a click to, with a paragraph tag inside of it:
<div id="testClick" style="width:200px; height:100px'>
<p>test click</p>
</div>
and I bind a click to the div:
$('#testClick').bind('click', function(e){
//with parent div (via e.target), do something
});
Now, if I click on the text inside the p tag, e.target = the p element, and if I click on the div (around, not on the text) e.target = the div element. So e.target = the object clicked on - i.e. the event is also bound to any children of the element specified.
This is to be expected, but I need operate on the parent div. and using e.target is not a reliable way of getting a reference to the div, because depending where within the div the click occurs e.target returns a different element. If I use e.target.parent to get a reference to the div, this fails when the click occurs in the div around the text.
Is there no simple way to get e.target to always and only return the exact element to which the click was initially bound?
(For example, in actionScript there is a property "mouseChildren" that prevents events from firing on children of bound elements)
(Consider all above pseudo code)
this will reference the element to which the handler is bound.
$('#testClick').bind('click', function(e){
alert( this.id );
});
DEMO: http://jsfiddle.net/yX499/
What happens is that the event bubbles up from the most deeply element clicked all the way to the document root.
If it encounters an element along the way with a handler bound for the type of event that occurred, it invokes that handler.
e.target will always reference that deeply nested element, but this will reference the element to which the handler is bound, so if the event finds 2 elements with an appropriate handler bound on the way up to the root, e.target will not change in the 2 handlers, but this will be different based on the bound element.
Use this. jQuery calls your callback with this set for your convience:
$('#testClick').bind('click', function(e){
$(this).doStuff();
});
Related
JavaScript
function productBox(event){
event.stopPropagation();
console.log(event.target);
}
var product = document.getElementsByClassName('product');
for (var g = 0, length = product.length; g < length; g++){
console.log('here');
product[g].addEventListener('click',productBox);
}
HTML
<div class="product">
<div class="productContent">
<img src="file:///C|/Users/Jacob/Downloads/12939681_1597112303636437_733183642_n.png" />
</div>
<div class="productName">
Here
</div>
<div class="productDescription">
</div>
So the problem lies in the fact that when the product element is clicked, event.target returns the actual child element of the event listener. For example, i click a "product" and it'll return productContent, productName or productDescription as the target, when actually what i want is the "product" element and then to do a .childNodes and find the image within that.
Please note jQuery is not an option, it is 30kb of stuff i won't use as this is a static html page with barely any javascript.
I've thought perhaps,
doing a check if the element is 'product' if not, take the parent and check if it's a 'product', if not go to that parent and so on. Then find the img tag within that. But i feel like that is a long winded work around.
Any thoughts?
To get the element to which the handler is bound, you can use this within the handler.
As #DaveNewton pointed out, the event object has a .currentTarget property that can be used as well. This is nice because you can have functions that have a manually bound this using .bind() or you may be using the new arrow functions, which have a this defined by its original environment, making it impossible to get the bound element that way.
You can use parentElement property of the target.
function productBox(event){
var target = event.target;
var parent = target.parentElement;//parent of "target"
//Rest of your code
}
I asked a similar question: if you have a element.click(function(){}) and the element has two or multiple siblings. If the event trigger is attached to the parent and not the target, this post popped up and is actually irrelevant to my question so I decided to post my search here for someone else.
I used this method:
if (target.closest('.NEftune').attr('rel') != undefined){
/*do something here*/
}
The closest method in jQuery and JavaScript starts from the clicked target and then bubbles up until it finds an attribute you looking for. In my case the event was attached to an Element with the class (.NEftune) and by adding an extra attribute I could determine if I was inside the container (.NEftune) which has an image inside.
I know it's a bit late, but the simplest solution to get the actual target to which the event handler was attached to is to use the event currentTarget property.
This will avoid unnecessary further DOM check and works out of the box on all modern browsers.
Event.currentTarget on MDN
The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred and which may be its descendant.
It seems that the mouseout callback from jQuery attached to the div tag containing several children elements will be called each time when user dragged a mouse away from any of these children elements. Am I right?
If so, how can I intercept the top-level tag mouse event only?
jQuery's mouseout bubbles, which means you can put an if statement to check and see if the target is the parent and put your code inside that.
https://jsfiddle.net/ck0kbowt/ for example :)
I am trying to understand this particular difference between the direct and delegated event handlers using the jQuery .on() method. Specifically, the last sentence in this paragraph:
When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.
What does it mean by "runs the handler for any elements"? I made a test page to experiment with the concept. But both following constructs lead to the same behavior:
$("div#target span.green").on("click", function() {
alert($(this).attr("class") + " is clicked");
});
or,
$("div#target").on("click", "span.green", function() {
alert($(this).attr("class") + " is clicked");
});
Maybe someone could refer to a different example to clarify this point? Thanks.
Case 1 (direct):
$("div#target span.green").on("click", function() {...});
== Hey! I want every span.green inside div#target to listen up: when you get clicked on, do X.
Case 2 (delegated):
$("div#target").on("click", "span.green", function() {...});
== Hey, div#target! When any of your child elements which are "span.green" get clicked, do X with them.
In other words...
In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each span is directly responsible for its own events.
In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated. This also means that the instruction will be carried out for child elements that are created in future.
The first way, $("div#target span.green").on(), binds a click handler directly to the span(s) that match the selector at the moment that code is executed. This means if other spans are added later (or have their class changed to match) they have missed out and will not have a click handler. It also means if you later remove the "green" class from one of the spans its click handler will continue to run - jQuery doesn't keep track of how the handler was assigned and check to see if the selector still matches.
The second way, $("div#target").on(), binds a click handler to the div(s) that match (again, this is against those that match at that moment), but when a click occurs somewhere in the div the handler function will only be run if the click occurred not just in the div but in a child element matching the selector in the second parameter to .on(), "span.green". Done this way it doesn't matter when those child spans were created, clicking upon them will still run the handler.
So for a page that isn't dynamically adding or changing its contents you won't notice a difference between the two methods. If you are dynamically adding extra child elements the second syntax means you don't have to worry about assigning click handlers to them because you've already done it once on the parent.
The explanation of N3dst4 is perfect. Based on this, we can assume that all child elements are inside body, therefore we need use only this:
$('body').on('click', '.element', function(){
alert('It works!')
});
It works with direct or delegate event.
Tangential to the OP, but the concept that helped me unravel confusion with this feature is that the bound elements must be parents of the selected elements.
Bound refers to what is left of the .on.
Selected refers to the 2nd argument of .on().
Delegation does not work like .find(), selecting a subset of the bound elements. The selector only applies to strict child elements.
$("span.green").on("click", ...
is very different from
$("span").on("click", ".green", ...
In particular, to gain the advantages #N3dst4 hints at with "elements that are created in future" the bound element must be a permanent parent. Then the selected children can come and go.
EDIT
Checklist of why delegated .on doesn't work
Tricky reasons why $('.bound').on('event', '.selected', some_function) may not work:
Bound element is not permanent. It was created after calling .on()
Selected element is not a proper child of a bound element. It's the same element.
Selected element prevented bubbling of an event to the bound element by calling .stopPropagation().
(Omitting less tricky reasons, such as a misspelled selector.)
I wro te a post with a comparison of direct events and delegated. I compare pure js but it has the same meaning for jquery which only encapsulate it.
Conclusion is that delegated event handling is for dynamic DOM structure where binded elements can be created while user interact with page ( no need again bindings ), and direct event handling is for static DOM elements, when we know that structure will not change.
For more information and full comparison -
http://maciejsikora.com/standard-events-vs-event-delegation/
Using always delegated handlers, which I see is current very trendy is not right way, many programmers use it because "it should be used", but truth is that direct event handlers are better for some situation and the choice which method use should be supported by knowledge of differences.
Case 3 (delegated):
$("div#target").delegate("span.green", "click", function() {...});
I have created a simple div demonstration below, that will display none once click.
<div id="three" onclick="toggle2(event);return false;" style="background:#303; color:#FFF;">
function toggle2(e) {
var textx = (e.target) ? e.target : e.srcElement;
textx.style.display = "none";
console.log(e.target);
}
my question is what is the difference if I replace
<div id="three" onclick="toggle2(event);return false;" style="background:#303; color:#FFF;">
with
<div id="three" onclick="toggle2(this);return false;" style="background:#303; color:#FFF;">
both of them work fine for my example abovee.....
It may well be that they are exactly the same. This depends on the HTML. In this case, this will always be the div element. this refers to the element where the event is captured. event.target, however, points to the element where the event originated.
If the element has no children, they will always be the same. If, however, you had something like this:
<div id="three" onclick="toggle2(event);return false;" style="background:#303; color:#FFF;">
<span>Line 1</span>
Line 2
</div>
then they may be different. A click on Line 1 will cause event.target to be the span element, so only that element will be hidden.
Unless you specifically want to point to the element where the event originated, it's more intuitive to use this.
You usually use e.target when "e" is the event like a click passed in parameter.
When you pass this as a parameter, this is the reference to the DOM node in which there is the javascript method. So here, "this" references the div.
And as you have a click event on your div, when you click on it, it is considered as an event, that's why this and e.target both work.
Moreover, "this" will always refer to you div, whereas "e.target" will reference an element you clicked in the div.
I think isn't necessary to pass this as argument on onclick event, you can use this direct to the function.
The event refers to the event currently being fired. Now in a browser the events are bubbled up from the element on which the event is fired to its parent until it reaches the document root. More at: What is event bubbling and capturing?
In your example, the event points to the click event and the event.target is the div and this refers to the div itself. If you add a child element to the div and click on the element then the event.target will point to the child element and this will still refer to the div.
I am not able to trigger event on element I clone/insert to the DOM.
Check the fiddle here.
HTML:
<div class="A">[THIS IS DIV A]</div>
<div class="B">CLICK TO CLONE DIV A</div>
jQuery:
$('.A').on("click",null,function() {
alert('DIV A CLICK');
});
$('.B').on("click",null,function() {
$('.A').clone().insertAfter('.A');
});
If I click on a cloned DIV A, nothing happens.
How can I trigger event on cloned element?
There are two solutions I propose.
Firstly, and the better option in this case, would be to add the true argument to clone(), for example in this case it would be:
$('.A').clone(true).insertAfter('.A');
As first argument of clone() represents:
"A Boolean indicating whether event handlers and data should be copied
along with the elements. The default value is false." - jQuery API section for clone().
This means all bound events will be kept on each cloned element.
jsFiddle here.
The second option, probably not the way to go for this example, would be to use on()'s event delegation feature:
"Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time." - jQuery API section for on().
In this case:
$(document).on("click", ".A", function() { ...
Now, every new element with the class .A will be have this click event attached to it, even cloned elements.
jsFiddle here.