I've been reading about replacing jQuery live() functions with on(), but they only work the same way as non-live functions.
For example, using the $('a').on('click', function(){}); has the same effect as using: $('a').click(function(){});
I need to replicate the functionality of $('a').live('click', function(){});
because I'm adding elements to the page dynamically.
You need to provide a selector:
$('#container').on('click', 'a', function(){})
Where #container is the id of the static parent of all the relevant anchors.
Try to attach the on to the closest static parent.
on 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.
The equivalent of:
$('a').live('click', function(){});
is this:
$(document).on('click', 'a', function(){});
But, .on() is more powerful because rather than attach all event handlers to the document object like .live() does, you can pick a static parent that is much closer to your dynamic objects and will be more efficient, particularly if you have a lot of delegated event handlers.
For example if you had a container div called links, you would do this:
$("#links").on('click', 'a', function(){});
The selector in the jQuery object is the static object that you want the event handler bound to. The selector in the arguments to .on() is a selector that matches the objects who's events you want to handle that will bubble up to your static parent object.
Related
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 always wondered which is the better way of handling events in terms of code manageability, cleanliness and code reuse.
If you use the former then say a list of 10 anchor tags with click handler will have something like:
Click Me
Click Me
Click Me
... 10 times
which looks kind of odd.
With the latter method, using anonymous function, it'd be like:
$('a').on('click', function(e){});
At the end of the day, every event is bound to some element in the DOM. In the case of .bind, you're binding directly to the element (or elements) in your jQuery object. If, for example, your jQuery object contained 100 elements, you'd be binding 100 event listeners.
In the case of .live, .delegate, and .on, a single event listener is bound, generally on one of the topmost nodes in the DOM tree: document, document.documentElement (the element), or document.body.
Because DOM events bubble up through the tree, an event handler attached to the body element can actually receive click events originating from any element on the page. So, rather than binding 100 events you could bind just one.
For a small number of elements (fewer than five, say), binding the event handlers directly is likely to be faster (although performance is unlikely to be an issue). For a larger number of elements, always use .on.
The other advantage of .on is that if you add elements to the DOM you don't need to worry about binding event handlers to these new elements. Take, for example, an HTML list:
<ul id="todo">
<li>buy milk</li>
<li>arrange haircut</li>
<li>pay credit card bill</li>
</ul>
Next, some jQuery:
// Remove the todo item when clicked.
$('#todo').children().click(function () {
$(this).remove()
})
Now, what if we add a todo?
$('#todo').append('<li>answer all the questions on SO</li>')
Clicking this todo item will not remove it from the list, since it doesn't have any event handlers bound. If instead we'd used .on, the new item would work without any extra effort on our part. Here's how the .on version would look:
$('#todo').on('click', 'li', function (event) {
$(event.target).remove()
})
Second method is preferrable, since we should not be mixing our JavaScript with the HTML. (Separation of Concerns) . This way your code is kept clean.
This also works well with dynamically inserted HTML code.
`$('a').on('click', function(e){});` // Using jQuery.
Using Vanilla JS:
document.getElementById("idName").addEventListener("click", function(){}); // Bind events to specific element.
document.addEventListener("click", function(){}); // Bind events to the document. Take care to handle event bubbling all the way upto the document level.
So I know that if you query an element by ID, it's way faster than by only class.
Is this true in the case binding events to dynamically created elements.
Example:
$(document).on('click', '#id .class', someFunction);
vs
$(document).on('click', '.class', someFunction);
Assume I have really a lot of elements on my page.
When I click on the element binded by the function above, will the 1st method call someFunction faster than the 2nd method?
If you understand how delegated event handling works in jQuery, then your first version with '#id .class' is just making more work for the event handling system. As others have said, you would have to run some tests to see if the difference is even measurable, much less consequential.
To help you understand, here's how the delegated event handling for this works:
$(document).on('click', '#id .class', someFunction);
An event handler for the click event is registered on the document object. Then, anytime a click event bubbles up to the document object, the jQuery system is called and it has to check to see if the event target matches the '#id .class' selector. This is a bit of work. First it has to see if the object itself (or a parent) matches .class, then it has to search up the parent chain from where it finds a match to see if it finds #id.
Your second version doesn't have to search up the parent chain for the #id. So, if that isn't required for accurately targeting only the items you want, then you should just go with your second option:
$(document).on('click', '.class', someFunction);
simply because it makes less work for the code to do.
I have a touchscreen page with an element <div id="x"> with several sub-elements in the form of:
<div id="x_1" style="certain_class" pid="1723464"></div>
<div id="x_2" style="certain_class" pid="1723465"></div>
<div id="x_3" style="certain_class" pid="1723466"></div>
<div id="x_4" style="certain_class" pid="1723467"></div>
These elements have a jQuery touchstart bind event set on them... Now if I alter these sub-elements using:
$("#x").html("<div id="x_1"></div><div id="x_2"></div><div id="x_3"></div><div id="x_4"></div>");
which changes these sub-elements to:
<div id="x_1"></div>
<div id="x_2"></div>
<div id="x_3"></div>
<div id="x_4"></div>
Why is it I seem to lose the touchstart bind set to these elements? Is it because I'm rendering the sub-elements within the parent element null and void, and thus the binding event?
Should I be changing the extra parameters on these elements bit by bit through the individual sub-elements within the parent node, rather than using the .html() method on the parent node?
Any help is greatly appreciated...
When you replace child elements like you are doing with the .html() jQuery method, then the old DOM elements that had event handlers bound to them are completely removed and no longer in the DOM. Thus, your event handlers that were on those elements are then gone.
You have several options:
Stop replacing the elements. Instead, just modify them so that the same elements stay in the page and thus their event handlers stay intact.
Reattach the event handlers after you set .html() to install new event handlers to the new elements.
Use delegated event handling that is attached to a parent object that is not destroyed/recreated.
My first choice is to stop replacing the elements if that is practical. I don't know exactly what type of change you're trying to have take place so I don't know how simple this would be.
Oftentimes, delegated event handling is the most elegant solution. You attach an event handler once to a parent and it will stay in effect even though you destroy and recreate the children. In this case, here's one way to do that:
$("#x").on("touchstart", "[id^='x_']", function(e) {
// event handler code here
// the this pointer will point to the origional DOM object that caused the event
});
This attaches a delegated event handler to the #x object and that event handler fires anytime the touchstart event happens on any child object whose id attribute starts with "x_".
Here are some other references on delegated event handling:
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?
JQuery Event Handlers - What's the "Best" method
Pretty much you are deleting the existing elements (and their bindings), and creating new ones with identical ids (but without bindings). One solution would be to use jQuery.fn.on to bind events. This way the binding will be part of x which is kept.
$('#x').on('touchstart', '#x_1, #x_2, #x3, #x4', yourFunction)
I'm creating and removing HTML from inside a div with jQuery (shopping cart, adding/removing items). I need to access the .click() event of a link inside this div, but it only works when the page first loads - after that link is removed then re-added it doesn't respond to any jQuery events.
I've tried functions both inside and outside of $j(document).ready(function() {}. How can I make events work on this link again after re-creation?
Use .delegate() instead of .click() (which is short-hand for .bind('click')):
$(<root-element>).delegate('a', function () {...});
Attach a handler to one or more events for all elements that match the
selector, now or in the future, based on a specific set of root
elements.
Source: http://api.jquery.com/delegate/
The <root-element> can be the document element or if you know an element that is always present that is a descendant of the document element it is best to use that element.
You need to either reattach the event every time you overwrite the content of your container div or set handler using live/delegate/on depending on the version of jQuery you use.
Second method is in general more elegant, but has drawbacks. In particular you cannot cancel the default action from cascaded even attached to the container.
The .click() event only works for elements that are present with the function is called. You need to look into using either .live() or .delegate() to attach listeners to elements that are dynamically created after $(document).ready()
Try using .detach() instead of .remove(), that will keep the events. Alternatively use event delegation.