Direct vs. Delegated - jQuery .on() - javascript

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() {...});

Related

How does this working jQuery code for .on() match the API documentation?

I am learning jQuery and have a very basic question.
How does the following jQuery code match the API documentation for .on():
$('body').on('mouseenter', '#jResult', function(e) {
...
});
According to the jQuery API for .on(), .on() can take the following form:
.on( events [, selector ] [, data ], handler )
I gather that the event is 'mouseenter' and the handler is the function, right?
However "selector" and "data" appear to be optional, correct?
Is '#jResult' a selector or data???
After you tell me if it is a selector or data, kindly tell me how I could have figured this out by reading the documentation?
It is probably something obvious, but I have read and re-read the documentation and just don't get it.
Please help...
That is the delegated event handler which replaced live() several years ago.
However "selector" and "data" appear to be optional, correct?
That is correct. If the selector parameter is omitted, then the event is not delegated to the parent element.
Is '#jResult' a selector or data???
#jResult is a selector. It is a child element of body on which the event will be listened for.
After you tell me if it is a selector or data, kindly tell me how I could have figured this out by reading the documentation?
It's in the on() documentation in this line under the description of the selector paragraph:
A selector string to filter the descendants of the selected elements that trigger the event
There's also a clearer description in the lower paragraph titled: Direct and delegated events.
The majority of browser events bubble, or propagate, from the deepest, innermost element (the event target) in the document where they occur all the way up to the body and the document element. In Internet Explorer 8 and lower, a few events such as change and submit do not natively bubble but jQuery patches these to bubble and create consistent cross-browser behavior.
If selector is omitted or is null, the event handler is referred to as direct or directly-bound. The handler is called every time an event occurs on the selected elements, whether it occurs directly on the element or bubbles from a descendant (inner) element.
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 that version of the code does is set up a selector for event delegation (<-- This documentation will help you to understand the syntax you have and the concept of how it works pretty clearly).
The mouseenter event is attached to the body element, but because of event bubbling, the event may be first triggered by a descendant of body. For example, if you click a link (which is inside of the body), then the click event will be initiated by the link element, but propagate upward, eventually reaching the body element.
Your code tests to see if the mouseenter event that is handled at the body element was initiated by the #jResult element.
how I could have figured this out by reading the documentation
This question can reworded as:
given that both selector and data are optional, how does the event handler know that you've provided a selector and not provided data?
This is covered in the documentation for on() (about half-way down) as:
Passing data to the handler
The data argument can be any type, but if a string is used the selector must either be provided or explicitly passed as null so that the data is not mistaken for a selector.
So in this case:
... .on('mouseenter', '#jResult', function(e) ...
#jResult is a selector as there is no data parameter (the last parameter is recognised as the handler as it's a function).
If you wanted to pass "#jResult" as data as a string, you would call it as:
... .on('mouseenter', null, '#jResult', function(e) ...
if data is not a string, then you don't need the selector, ie:
var d = {};
d.value = '#jResult';
... .on('mouseenter', d, function(e) ...
but that may confuse other developers who would be expecting a selector as the 2nd arg.

In jquery, what is the difference between these two on clicks? [duplicate]

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() {...});

jQuery :last-child not updated as additional children are added?

I have a set of inputs where I would like to add another input when the last input currently displayed receives focus. The form starts out with 2 inputs.
$('.answer_fields input:last-child').focus(function(event) {
var nextfield = $('.answer_fields input').length + 1;
$('.answer_fields').append('<input class="form-control answer_field" placeholder="Answer option #' + nextfield +'" id="answer' + nextfield +'">');
});
As it currently stands, additional fields are only appended when the 2nd input (the original "last-child") receives focus.
Checking the source seems to show that the inputs are getting added to the DOM as expected. What am I missing?
The jQuery selector is evaluated ONCE at the time you run the code to install the .focus() event handler. It isn't adjusted live as things change. You can switch to event delegation if you want that type of behavior.
A similar event handler using event delegation that will be evaluated live would look like this:
$(some common parent selector).on("focus", ".answer_fields input:last-child", function() {
// code here
});
Working demo: http://jsfiddle.net/jfriend00/ZZLPJ/
First off, your original code did not work because you were attaching event handlers to specific elements that existed at the time you ran your first code. Which events you had event handlers attached to did not change as your structure changed in the future so new elements would not have event handlers on them and the :last-child portion of your selector would not be re-evaluated.
Event delegation works because certain events that occur on a given element will bubble up the DOM hierarchy through each parent. You can attach a single event handler to a common parent and when that parent is notified that a given event occurred, it can then dynamically evaluate the selector you specified vs. the child item that originated the event and if it's a match, fire your event handler. This gives you the "real-time" selector evaluation that you want in your case.
To give you an idea, these are what I call "static" event handlers:
$(".test:last-child").focus(fn);
$(".test:last-child").on("focus", fn);
This a delegated (and thus dynamic) event handlers:
$("#parent").on("focus", ".test:last-child", fn);
For more info on this topic, see these other answers:
jQuery .live() vs .on() method for adding a click event after loading dynamic html
Does jQuery.on() work for elements that are added after the event handler is created?
Should all jquery events be bound to $(document)?
Just checked to be sure and the focus event does bubble event delegation will work with it.
This is a common question for people starting to use jQuery. It turns out that the named eventListeners (i.e. $.fn.click, $.fn.focus, etc.) attach an event listener to each matching DOM element when the page is first loaded. These listeners are defined on the matching element themselves, meaning that each one is unique to the element it is attached to.
You will hear this method compared to what is commonly known as event delegation. Event delegation involves attaching an even listener to a shared parent element that will check the event.target attribute to see if it matches your criterion (in your case, .answer_fields input:last-child). This differs from attaching a unique listener to each DOM element you wish to target because it allows you to add new nodes to the DOM dynamically, which will then be handled by the parent node's listener like any other node.
To accomplish event delegation using jQuery, use the .on() function on a common ancestor (I'll use document as an example) and listen for all matching elements as the second argument of the function call:
$(document).on('focus', '.answer_fields input:last-child', function(event) {
// your code here
});
Not only will this solve your problem with dynamically created DOM elements, but event delegation will greatly improve the performance of your page by reducing the total number of event listeners attached to elements in the DOM.
For more information about event delegation, I'd encourage you to check out this tutorial and this SO question

jQuery clone() not cloning event bindings, even with on()

I have created a series of custom jQuery events for use in mobile web applications. They work great and have been tested. However, I have run into a small problem which I am having trouble understanding.
I am using .clone() on a few elements within the DOM, which contain a button. The button has some of the custom events bound to it (the events are bound using .on()), but. Unfortunately, when I use jQuery's .clone(), the bindings are not preserved, and I have to add them again.
Has anyone encountered this before, does someone know of a potential work around? I thought that using .on() was supposed to preserve the binding for elements that exist now, or in the future?
I think you should use this overload of the .clone() method:
$element.clone(true, true);
clone( [withDataAndEvents] [, deepWithDataAndEvents] )
withDataAndEvents: A Boolean indicating whether event handlers and data should be copied along with the elements. The default value is false.
deepWithDataAndEvents: A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false).
Beware that .on() does not actually bind the events to the targets but to the element you are delegating to. So if you have:
$('#container').on('click', '.button', ...);
The events are actually binded to #container. When a click on a .button element occurs, it bubbles up to the #container element The element which triggered the event is evaluated upon the selector parameter of .on() and if it matches, the event handler is executed. This is how event delegation works.
If you clone the element #container, you have to deep clone with events and data for the bindings made with .on() to be preserved.
This would not be necessary if you were using .on() on a parent of #container.

Binding event to the element in JavaScript

Generally, when I want to bind some event to an element, I will bind the event to the element directly. For example, I want to bind the click event to the "li" element:
<ul id="ul_list">
<li class="t">xxxx</li>
<li class="t">xxxx</li>
.....
</ul>
var lis=document.getElementById("ul_list").children();
for(var i=0;i<lis.length;i++){
lis[i].onclick=function(){
console.info(this.innerHTML);
}
}
It works.
But in some open source code, I find that people prefer to bind the event to the parent element:
document.onclick=function(e){
e=e==null?:window.event:e;
var target=e.target; //the browser is not considered here
if(target.className=='t' && target.localName='LI'){
console.info(target.innerHTML);
}
}
I wonder which is better?
Also, when handling drag events, people bind the mousemove event to the whole document. Why?
people prefer to bind the event to the parent element
This is referred to as event delegation and is especially useful when you want to trigger the same event handler for multiple elements. Instead of binding an event handler to each those elements, you bind it to a common ancestor and then check from which element the event originated. This works, because events bubble up the DOM tree.
I wonder which is better?
It depends, both approaches have their pros and cons.
Event delegation can be slower, as the event has to bubble up first. You also might have to perform DOM traversal because the event might not originate at the element you test for. For instance, in your example, the li elements might have other children, lets say an a element. To find out whether the clicked a element is a descendant of one of the lis, you have to traverse the ancestors and test them.
On the other hand, binding the handler directly is faster in the sense that the event is processed directly at the element. But if you bind a lot of event handlers and don't do it properly (like in your example) you use more memory than you actually need. Older browsers (I think especially IE) might also perform worse if there are many event handlers.
Also,sometime when handle the drag effect,people always bind the mousemove event to the whole document,why?
The problem is that while dragging an element, the mouse often moves faster than the element and leaves it. If you bind the mousemove event only to the dragged element, whenever the cursor leaves the element, the movement would stop. To avoid this, the event handler is bound to the whole document (for the duration of the dragging) so that the movement is smoothly.
Linking to the parent means you are adding one event handler instead of multiple. That is a boost for performance in your application! Also if you add new elements to the page, you do not have to worry about binding them.
Only time when that model is a bad design is when you need to cancel the event. For example, you have links in the li and you need to prevent them from doing their default action.
In case of click event, it's better to bind directly to the affected elements, no point binding to everything - why do you need to have your function trigger when you click something that is totally not relevant to you?
It can be useful when the elements are spread over the document and hard to "collect" them, e.g. when they only have the same class - getElementsByClassName is not very efficient in pure JavaScript as you need to iterate over all the elements, so in such case it's better to trigger the function always and check what has been clicked.
Bind handlers to the most specific event and element possible.
Note that (just to be pedantic!) you bind a handler to an event for an element, not 'bind some event to an element'.

Categories

Resources