I've got a page with some Javascript / jQuery stuff, for example:
(function()
{
$('.tip').tooltip();
$('.test').click(function()
{
alert('Clicked!')
});
}();
On the page I insert some HTML with jQuery so the DOM changes. For example, I insert a extra element with the class "tip" or "test". The just new inserted elements doesn't work because jQuery is working with the non-manipulated DOM and the just inserted elements aren't there. So I've searched around and came to this solution for the "click":
$('body').on('click','.click',function()
{
alert('Clicked!')
});
I don't understand why, but this way it's working with the manipulated DOM and the jQuery stuff works on the new inserted elements. So my first question is, why does this work and just the click() function not? And the second question, why do I have to point to the "body"?
Finally, my third question is, how get this done with the tooltip?
I know that there is so many information about this subject (previous the delegate() and live() function I've read) but I can't found a explanation about it. And I can't get my third question solved with the information I found.
I'm looking forward to your responses!
Extra question:
4) Is it recommended to point always to the "body" for this kind of situations? It's always there but for possible performance issues?
So my first question is, why does this work and just the click()
function not?
Because the event handler is now delegated to a parent element, so it remains even after replacing/manipulating child elements.
Ancient article on event delegation for your perusal - but the concepts remain the same:
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
And the second question, why do I have to point to the "body"
You don't, any suitable parent element will do. For example, any direct parent (a div wrapper, for instance) which does not get replaced.
Finally, my third question is, how get this done with the tooltip?
You need to re-initialize your tooltip plugin on the newly inserted elements. For example:
$.get("foo.html", function (html) {
$("#someDiv").html(html);
$("#someDiv").find(".tip").tooltip();
});
The click() event doesn't work when you manipulate the DOM because JQuery is not watching for DOM changes. When you bind the click() event it is selecting the elements that are on the page at that time. New ones are not in the list unless you explicitly bind the event.
Because you have pointed the click() event on the body. JQuery then checks to see if the target of the click matches any of the event handlers (like what you have created) match the element clicked. This way any new elements will get the event 'associated' with them.
Because the tooltip isn't an event that you can place on the body, you will need to re-initialize it when the element is created.
EDIT:
For your fourth question, is it depends. The advantage of binding to the body is that you don't accidentally bind an event to an element more than once. The disadvantage is that you are adding event handlers that need to be checked on each event and this can lead to performance issues.
As for your concerns about DRY, put the initialization of the tooltips into a function and call that when you add them. Trying to avoid having the same function call is a little overkill in this regard, IMO.
Events are bound to the specific object you are binding it to.
So something like $('.tip').tooltip() will perform the tooltip() functionality on $('.tip') which is actually just a collection of objects that satisfies the css selector .tip. The thing you should take note of is, that collection is not dynamic, it is basically a "database" query of the current page, and returns a resultset of HTML DOM objects wrapped by jQuery.
Therefore calling tooptip() on that collection will only perform the tooltip functionality on the objects within that collection, anything that was not in that collection when tooltip is called will not have the tooltip functionality. So adding an element that satisfies the .tip selector, after the tooltip() call, will not give it the tooltip functionality.
Now, $('body').on('click','.click', func) is actually binding the click event to the body tag (which should always exist :P), but what happens is it captures whether the click event has passed through an element your target css selector (.click in this case), so since the check is done dynamically, new elements will be captured.
This is a relatively short summary of what's going on... I hope it helped
UPDATE:
Best way for your tooltip thing is to bind tooltip after you have added elements, e.g.
$('#container').load('www.example.com/stuff', function() {
$('.tip', $(this)).tooltip();
});
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() {...});
So I'm appending a row in a table and then upon clicking a button I want to remove that row.
However I can't do it in a way that seems natural, like:
$(".usun").on("click", function(){
$(this).parent().parent().remove();
});
where .usun is a class of buttons that remove a row in a table. Of course this fragment of code is inside of $(document).ready(.... What seems to work for me is this:
$(this).on("click", ".usun", function(){
$(this).parent().parent().remove();
});
and I don't understand why. Could someone explain me this?
There are several method signatures available for .on() which you can read about here.
Your first syntax ($(".usun").on() is, just one time, attaching click handlers to each individual .usun element. If more are added later, they don't have click handlers.
The second syntax ($(this).on("click", ".usun", function()...) attaches a single listener to the document, saying "any time a .usun inside me is clicked, do something". This covers a case where more matching elements are added after $(document).ready is fired.
That is because that specific .usun does not exist yet when you are registering the event.
You need to register the event to its parent element (prefereably the table id) like so:
$('#yourtableid').on("click", ".usun", function(){ //if table id does not exist, you can use $('body') instead
$(this).parent().parent().remove();
});
This will make sure that you are attaching the events even to 'unborn' elements.
Hope this helps!
So you're aware of this... your question is related to live and non-live lists (collections) in javascript;
review the following Javascript,
var queryStatic = document.querySelectorAll(".usun"); //non-live | static list
var queryLive = document.getElementsByTagName("div"); //live list
queryStatic will return a NodeList that statically references the nodes that satistifed the condition of the query.
If you were to alter/modify the properties/fields/members of a node or it's element that is referenced by this NodeList outside of using the queryStatic, it would be represented in the queryStatic instance;
you cannot, however, add a new element/node to the DOM that would have satisfied the query and expect it to be in that NodeList.
queryList (an HTML Collection), however, will represent both aspects. It is a live list.
The point is, the underline concept has little to nothing to do with method signatures.
Edit
However, in the case of jQuery's .on() method; there is support for attaching the listener to a parent element (like Document) and specifying an additional query criteria.
I have some javascript that successfully clones a template and appends the resulting html to a div. However when I try to reference an element of the clone it is not accessible, even though if I place the exact same element with the exact same ID (confirmed with Firebug) outside the template (and the cloning system) it is accessible. I believe I need to do an update of some kind after cloning but I am not sure. The code I am trying to use to access the (cloned) element (does not log anything to console and is not working) is:
$("#depminusbutton0").on("click", function () {
console.log('I triggered minus 0');
});
And depminusbutton0 shows up like this in firebug inspect element once cloned (doesn't exist prior to cloning, as ID 0 is inserted dynamically:
<a id="depminusbutton0">
Any ideas how I can make this element accessible?
Two possibilities I can think of:
You are installing the event handler before the element exists so it can't find the element to attach the event handler to?
You have a conflicting ID elsewhere in the document.
If you're going to use this form of event handling:
$("#depminusbutton0").on("click", fn);
Then, the #depminusbutton0 element must exist at the time you run that line of code. It will search the DOM for that element at the time you run the code and will not hook up to an element that matches that ID that you create in the future.
You can work around that issue, either by running that line of code AFTER you create the #depminusbutton0 element and insert it in the DOM or you can switch to use delegated event handling which attaches the event handler to a common parent that does exist before you've created the child element.
To see more about how delegated event handling works, see these references:
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?
The general idea would be like this:
$(some parent selector).on("click", "#depminusbutton0", fn);
If you have multiple elements with the #depminusbutton0 id, then you will have to fix that and only have one element with that id. Often times with clones, you want to use a class name rather than an id since you can have multiple elements with the same class name.
Are you attaching the event to an element that doesn't exist yet? As described in the jQuery documentation:
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().
Just make sure you are attaching the event to the cloned element after you create it.
$(document).on('click', '.class-or-id-name', function() {
console.log("Heyyy its clickable");
// do more stuff
});
I want this webpage to highlight certain elements when you click on one of them, but if you click anywhere else on the page, then all of these elements should no longer be highlighted.
I accomplished this task by the following, and it works just fine except for one thing (described below):
$(document).click(function() {
// Do stuff when clicking anywhere but on elements of class suggestion_box
$(".suggestion_box").css('background-color', '#FFFFFF');
});
$(".suggestion_box").click(function() {
// means you clicked on an object belonging to class suggestion_box
return false;
});
// the code for handling onclicks for each element
function clickSuggestion() {
// remove all highlighting
$(".suggestion_box").css('background-color', '#FFFFFF');
// then highlight only a specific item
$("div[name=" + arguments[0] + "]").css('background-color', '#CCFFCC');
}
This way of enforcing the highlighting of elements works fine until I add more html to the page without having a new page load. This is done by .append() and .prepend()
What I suspected from debugging was that the page is not "aware" of the new elements that were added to the page dynamically. Despite the new, dynamically added elements having the appropriate class names/IDs/names/onclicks ect, they never get highlighted like the rest of the elements (which continue to work fine the entire time).
I was wondering if a possible reason for why my approach does not work for the dynamically added content is that the page is not able to recognize the elements that were not present during the pageload. And if this is a possibility, then is there a way to reconcile this without a pageload?
If this line of reasoning is wrong, then the code I have above is probably not enough to show what's wrong with my webpage. But I'm really just interested in whether or not this line of thought is a possibility.
Use .live to "Attach a handler to the event for all elements which match the current selector, now and in the future". Example:
$(".suggestion_box").live("click", function() {
// means you clicked on an object belonging to className
return false;
});
Also see .delegate, which is similar.
Since the .live() method handles events once they have propagated to the top of the document, it is not possible to stop propagation of live events. Similarly, events handled by .delegate() will always propagate to the element to which they are delegated; event handlers on any elements below it will already have been executed by the time the delegated event handler is called.
from the jQuery documentation =)
(only to explain better why #karim79 also suggested the delegate method ;P )
I'm trying to build a greasemonkey script which will dynamically create tables of data based on user interaction with... other dynamically created tables of data. My problem is that I'm having to make two passes every time I create a table: one to create the table, and another to go grab all of the objects in the table I want to add event handlers to (by id) and add the various event handlers to them.
If I attempt to, say, add an onClick event to a table td before I've created the table and inserted it into the HTML, I get a "component is not available" exception.
This is incredibly cumbersome, because I either have to maintain, separately, a list of the ids and what I should do to those elements when I make my second pass to add the handlers, or develop a naming convention by which I know, based on the id, what I should do with the element.
There HAS to be a better way to do this. I just haven't figured it out yet. Anyone have any ideas?
Firstly, I'd love to know why you need a different ID for every single TD. Is the ID holding important information, such as an index? In this situation it might be better creating each TD within a loop. Also, obviously you can't attach an event handler to a DOM element which doesn't exist! It doesn't have to be injected into the DOM but it DOES have to exist in some capacity.
jQuery's live() isn't a magical mystery, it just uses event delegation, so it attaches the event to a parent element, such as the table and then decides what happens dependent on the target of the click. Here's a rudimentary example. I register a handler to the 'body' element, and then I test each time to see what the target is, if it's a TD element I doSomething() ->
document.body.onclick = function(e) {
var realTarget = e ? e.target : window.event.srcElement;
if ( realTarget.nodeName.toLowerCase() === 'td' ) {
doSomething();
}
};
Event delegation relies on something called event bubbling (or "propogation") which is the way in which modern browsers implement the event model. Each event, when triggered will travel upwards through the DOM until it can go no further. So if you click on an anchor within a paragraph the anchor's 'click' event will fire and THEN the paragraph's 'click' event will fire etc. etc.
jQuery 1.3+ has a new live() function that can set up event handlers for elements that don't exist yet .. check it out
You have to wait for the element to be added to the page, then add the event handler then.
There is no easy way to say "add this to all elements of this type, now and in the future".
It is possible to have a timer periodically check the page for new elements, applying a queue of events (or other properties) to them as they appear, all behind the scenes. This can be abstracted out and re-used, for example Jquery can do that sort of thing.
As JimmyP pointed out, your problem can easily be solved using event bubbling. You might consider writing a wrapper function to work around browser inconsistencies - my own version can be found here and would be used like this:
capture('click', '#element-id', function(event) {
// `this` will be the originating element
// return `false` to prevent default action
});