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

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

Related

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 on() docs

Are the docs for jQuery's on() function incorrect (or unclear)? Consider this code:
<div>
<span>
<div>
<input type="button" value="click me!" />
</div>
</span>
</div>
$(document).on("click", function() {
console.log(this.toString());
});
The docs state
selector A selector string to filter the descendants of the selected
elements that trigger the event. If the selector is null or omitted,
the event is always triggered when it reaches the selected element.
Clicking the button only causes one console.log for the document itself, while $(document).on("click", "*", function()... causes many.
I know the Stack Overflow community isn't responsible for the jQuery docs, but shouldn't they say that when the selector is omitted, the event is only triggered when it reaches the selected element? Or is there something about event delegation I'm not understanding correctly?
Complete fiddle
There is no event delegation when you leave the selector out. From the docs:
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.
Basically .on is doing the jQuery thing -- it is overloaded to do completely different things depending on arguments. Personally I prefer .delegate for delegation, and .bind for normal events, as they are much clearer and I hope they don't get removed in later versions.
Personally I think it's clear enough. When the selector is omitted the event is triggered when it reaches the selected element. The word "always" doesn't really change the meaning in my opinion. The event will always be triggered when it reaches the selected element (note that if something like stopPropagation is called, the event will not reach the selected element and therefore won't be triggered).
When a selector is present, the event is triggered when it reaches the selected element having originated from an element matching the selector.
When you use the universal selector * every single element between the event target and the selected element will trigger the event.
As you stated in your comment, on provides all the functionality you need to bind events in jQuery 1.7+:
As of jQuery 1.7, the .on() method provides all functionality required
for attaching event handlers.

Direct vs. Delegated - jQuery .on()

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

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'.

Are defining many jQuery event handlers "expensive?" Internally Implemented at C/C++ or JavaScript level?

I have a long a web form. I'm wondering if I should bind() to each <input> element separately (what I really want) or should I only define one bind() all of the input elements then do an if-then inside the handler to handle the specific element?
jQuery's event handlers are implemented in JavaScript (they have to be).
In general, it's best to use a single handler for the entire form, rather than a large number of individual elements. But jQuery's live feature can help quite a lot, it does a lot of the plumbing for event delegation for you. For example:
$("#formid input").live('focus', function(event) {
var field = $(this);
// `field` now references the field that was focussed
});
...watches all fields in your formid form for focus events. If you're using jQuery 1.4, that works even with the focus event in that example (even though focus doesn't bubble, and so is usually tricky to use with event delegation). Prior to 1.4 that won't work, but bubbling events like click and such do.
I would bind to all the inputs, but you can use classes to narrow it down to just the inputs you want.
Instead of
$("#formid input").click(function() {
if($(this).attr('something') == 'special_field') {
//do A
}
});
add a class to the elements you are interested in <input class='special_field' ... >, and change your selector:
$("#formid input.special_field").click(function() {
//do A
});
That will limit the handler to only the desired elements without any if statement checks.
You can repeat this for anything that needs special handling.
Final note on the .live() method. It can be really handy for a couple reasons:
It doesn't actually create a binding for every matching element, it creates a listener on the body element by default(you can choose another element as the listener) and waits for an even to bubble up to it. When it bubbles, it checks the triggered element against the selector, and if its a match fires the event.
Because the selector is applied when the event fires, it means that new elements added via Javascript or AJAX will fire the code without needing to bind or rebind them.
There is one listener for many elements, meaning for large sets it can be much quicker.

Categories

Resources