jQuery event binding with dynamically loaded elements - javascript

I know of and research multiple ways to bind events .on(), .live(), .click(), etc. I know .live() is deprecated in 1.9+. The problem I'm encountering is binding dynamic DOM elements to events.
I have a div that contains links and is paginated. The links are dynamically loaded with AJAX. I want to overwrite binded events for these links so I used .unbind() and tried .on(). The problem is that these dynamically loaded links are not binded. I guess that's because the selector $('#id_of_links') is cached with the previous set of links.
QUESTION:
Is it possible to bind all elements that are loaded on the page, at any point in time, without having to set a callback when the user clicks next page on the links?

for dynamically added elements, you must bind the function to one of it's parents
$('#PARENT').on('click', '#DYNAMICALLY_ADDED_CHILD', function(){ CODE HERE });
Parent should already exist in the DOM tree...
More info on: http://api.jquery.com/on/

You cannot bind all elements, even those not loaded in the page yet, without having a callback method/function or a function that would loop in and keep checking if the elements with a specific attribute or characteristic would have the proper function binded to it, which would probably cause a memory leak over time.

Related

Binding to dynamic elements

I have some dynamic content that I am loading in via jQuery .load(). I have been trying to utilize the .on method, since the .live method is deprecated to bind elements to the page before they actually exist. I was able to achieve that but in this particular case the method I am invoking is firing twice. Any ideas and or solutions would be greatly appreciated.
I have tried unbinding('click') before binding and that seem to work but it causes the item I am trying to click on to fire on the second click. I also tried event.stopPropagation with no luck. Below is the code I currently have in place. I am currently utilizing the setTimeout approach until I can find a suitable solution.
$('#content').unbind('click').on('click', '.alternate-options', function(event){
//setTimeout(function(){
$('.alternate-option').each(function (index) {
$(this).bind('click', function (event) {
event.preventDefault();
$('.alternate-options li').each(function () {
$(this).removeClass('current');
});
$(this).addClass('current');
});
});
//},100);
});
Joel's answer is correct, but I thought it would be useful to explain it a little bit for others that might come across this in the future.
The deprecation of live() caused a lot of confusion for folks, but the replacement recommended technique really is a lot better once you understand it well.
First, the basics. If you're dynamically adding DOM elements to the page, you have to choose an approach to handling events on them.
One approach is to manually add event handlers to each element as they are dynamically added, typically you'd either add a data-* attribute to indicate the unique attribute of the dynamically added element, or you'd stick an object onto the DOM element itself that can be later retrieved in the event.
The old jquery approach let you simplify this process by using the live() API. In this case, you'd have a selector, and the DOM would be monitored for changes to that selector. This was pretty simple from the developer's perspective but had some major drawbacks. For more information this SO post describes some of the issues well: What's wrong with the jQuery live method?
The newer, and better approach is to use the on() method and look at a parent container DOM element, with an optional selector as a filter.
The reason why this works well is because it uses the normal DOM event bubbling behavior. When you trigger a bubbleable event (like click) on an element, that event will naturally "bubble" up the DOM, with a chance to catch it on each parent element all the way up the scene graph.
In the case of dynamic elements, this works really well. If you have a div, for example, that you're listening for a click event on, that event will be caught for any click events that were triggered by children, no mater when they were added to the DOM.
It would be a little annoying, however, to have to do a bunch of "if" statements inside of that click handler to determine if the element that was clicked was the one you're interested in. This is why the smart folks on the jquery team added the optional filter argument to the on() function. It let's you filter out events that were triggered by elements you don't care about.
This is why Joel's simple example works: you don't need to worry about directly adding event listeners to child elements or anything like that, you are just listening to the events on the container and filtering on the specific elements you care about.
This should do what you want it to:
$("#content").on("click", ".alternate-option li", function () {
$(".alternate-option li.current").removeClass("current");
$(this).addClass("current");
});
No need to rebind events or anything.
Here's a fiddle illustrating this binding with the dynamic content (both adding new list item or completely replacing the entire list):
http://jsfiddle.net/2jKCL/

Newly appended objects don't respond to jQuery events from page load

I am using Backbone here, but I've also experienced this issue with non-Backbone sites as well.
Essentially, my issue is that I define in one javascript file, base.js, a number of 'global' jquery functions. Then, I load new elements and append them to the page using Backbone, AJAX, or whichever asynchronous call you like. This works great, except the new objects don't seem to be linked to the jQuery events I declared on pageload. (Sorry for the layman language here - often I am a newbie at proper wording)
For example, let's say I declare in a js file loaded on pageload:
$('.element').hover(function(){alert('hi world')});
But then I append a new element after pageload, then this hover won't work.
Can anyone explain:
Why this is?
Whether I can force a new appended element to work with/listen to current events already declared?
I suspect (2) may not be possible (that I have to rewrite the events after appending), but am interested to know if there is some sort of solution.
Why this is?
Because when the event binding code is executed, elements that do not exist in the DOM already will not be affected (how could they be? They don't exist yet!) The "normal" event binding methods (e.g. .click() or .change()) will attach a copy of the event handler function to every element in the matched set.
I can force a new appended element to work with/listen to current events already declared?
You can indeed, by delegating the event handler higher up the DOM tree. Since most DOM events bubble up the tree from the element on which they originate, this works well. With jQuery, you can use the .on() (jQuery 1.7+ - for older versions use .delegate() instead) method to do this:
$("#someCommonAncestor").on("someEvent", ".element", function () {
// Do stuff
});
The element on which you call .on() must exist in the DOM at the time the code runs. When the event bubbles up far enough to reach whatever #someCommonAncestor may be, jQuery will check to see if the target element matched the selector you passed to .on(). If it does, it will execute the event handler. If it doesn't, the event will continue bubbling to the root of the document and not trigger any handler.

Using jQuery one() function as a hack to prevent multiple click events from firing

I've having this weird issue where toggling the visibility of a button seems to influence how many times a click event gets called on that button.
If I use: $('button').on('click'...), to click the button only 1 click event happens as long as prior to clicking it I had done a full page load. If instead, I load a page fragment containing the button via AJAX then click the button, the on() function fires off multiple click events, an added one for every time I load the page this way.
I can limit these multiple clicks from firing by using $('button').one('click', ... But i'm wondering if this is too hack-ish and it'd be better to fix what's causing the multiple click events from firing in the first place.
Has any run into this problem before of toggling visibility -/+ AJAX page loading affecting how many click events get fired on a click handler?
I guess its not at all hack-ish coz the very first line of the jQuery Docs for One() describe the purpose this function is designed for.
Attach a handler to an event for the elements. The handler is executed
at most once per element.
This easily says that it prevents multiple raises of events.
The internal working principle of this function is also same as mentioned by #mgraph, its mentioned in the docs:
The first form of this method is identical to .bind(), except that the
handler is unbound after its first invocation. The second two forms,
introduced in jQuery 1.7, are identical to .on() except that the
handler is removed after the first time the event occurs at the
delegated element, whether the selector matched anything or not.
A bit more detail -
This method normally binds an event to a handler using .bind()/.on() and as soon as the event occurs once and only once, it unbinds the event using .unbind()/.off()
If you're loading an element via AJAX, you want to use .on()'s binding property to tie it to an element that exists when the page loads. For example, $("body").on("click", "p", ... will bind the click element to p tags not only when the page has loaded, but also after any AJAX calls. If you simply do $("p").on("click", ... this only binds the click event to p elements when the page had loaded. So in the first example, you pick a parent element (hopefully more specific than the body) of the element you want to bind to, and then specify the element within the call to .on(). So in your case you may want to try, $('body').on('click','button', ...).
Oh, and as a side note, as of jQuery 1.7, the .on() and .off() methods are preferred to attach and remove event handlers on elements over .bind() and .unbind().

When using jQuery on(), why use (document) vs. the element itself?

I'd like a jQuery expert in their own words to explain why the $(document) identifier is recommended by others for jQuery's on() statement vs just using an element itself
Example 1: Why is using $(document) here better then Example #2?
$(document).on("click", ".areaCodeList a", function(){
// do stuff
});
Example 2: Why is using the element this way considering not good practice compared to Example 1?
$(".areaCodeList a").on("click", function(){
// do stuff
});
Both of those are valid.
The former works for dynamically added elements. You use document because you're delegating events on children of the document object, so events bubble up to the document level. It's also more convenient to select the closest parent you can (and the parent must exist on the page at load).
The latter still works, and is a preferred way to simply bind events to specific elements.
I personally don't recommend delegating through the document object, but rather the closest parent that exists on page load.
Here are the docs for on().
This is not true.
Those two lines do totally two different things.
The first one is a delegate event with the selector of ".areaCodeList a" while the second line is an event attached to the ".areaCodeList a" elements.
A delegate event will fire to every ".areaCodeList a" element although it was in the DOM when that line executed.
Anyway, attaching delegate events to the document isn't recommended at all. as written in the live docs:
Since all .live() events are attached at the document element, events take the longest and slowest possible path before they are handled
Please read the on docs:
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(). To ensure the elements are present and can be selected, perform event binding inside a document ready handler for elements that are in the HTML markup on the page. If new HTML is being injected into the page, select the elements and attach event handlers after the new HTML is placed into the page. Or, use delegated events to attach an event handler, as described next.
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time. By
picking an element that is guaranteed to be present at the time the
delegated event handler is attached, you can use delegated events to
avoid the need to frequently attach and remove event handlers. This
element could be the container element of a view in a
Model-View-Controller design, for example, or document if the event
handler wants to monitor all bubbling events in the document. The
document element is available in the head of the document before
loading any other HTML, so it is safe to attach events there without
waiting for the document to be ready.
...
...
I think you are confusing a few concepts. It is not recommended to bind to the document element, however there are times when you want to do so e.g when binding events to dynamically added elements.
All this may seem unclear, so here is the first example which uses the class selector directly and binds a click event, the element is inserted dynamically later after the event is bound. As you can see the event never gets fired because we selected an element that was not present in the DOM at the time the event was bound. This is equivalent to a .click
Now look at this second example. Here you see we are defining the root element as the document. Which means that the click event will bubble all the way up the DOM tree and then fire if the element that was clicked has a class dynamic. This is equivalent to .live
Now, if in example one, at the time of binding the event the element was present in the DOM, the code would work just fine, as you can see here.
That being said. Here's an except from the docs which clarifies the behavior above.
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()
So, in conclusion. Use the document element when you are sure that there's no parent element for the element you are selecting that's guaranteed to be in the DOM at the time th event is bound. If there is a parent element that is present then use that instead of the document element. This way the event will not have to bubble all the way up the document, it needs to travel only a short distance.
There's nothing "recommended" about this. The first snippet sets up a "delegated" event, the latter is a "direct" one. The documentation for on() explains these in depth.
Delegated events are necessary when you need to listen to events for elements that don't exist yet - ones that will be created dynamically after, for example, an AJAX call. They can also sometimes be better performance-wise - you need less memory to attach a "real" event handler to the document object, than to 1000 buttons.
I'd say it's still preferrable to use direct event handlers when you can, or attach delegate events to an element as close to the real event sources as you can. Having all event handlers on the document object is probably terrible for performance - you have to match every event fired against all the selectors. It's also probably needed if you need to stop an event from bubbling - if all events are caught on the document, they've already bubbled as far as they'll go.
Actually the best solution in a case such as this is not using the $(document) neither the specific element like $("selector").
The best approach is using the container of the element and bind the element selector in the on function. By doing this you avoid unnecessary event bubbling to the document.
So the code should look like:
$("this_is_the_container").on('event','my_element_selector',function(){
// do some stuff here
})
$(*selector*).on(*event*, function(){})
will apply only for elements which is already load in page at the moment of script run. If in future, will appear new elements, the event handler will be not work.
$(document).on(*event*, *selector*, function(){}
will execute event handler even the elements with same selector will appear on page after script run.
So, if you have some elements, which can appear after in random time, use
$(document).on()
else use
$(*selector*).on();

JavaScript handling events after the page loads

When I'm writing some JavaScript I have a set of interface buttons that have their events assigned when the page is loaded. My problem is anything created dynamically wont receive these events.
For example, I'm making a URL checker, whose job is to make sure that any link that goes to another domain brings up an exit interface letting the user know they are leaving. Any links created after the page is loaded, post ajax (no pun intended) or whatever wont have that event naturally as those that existed when the page loaded.
In practice, what's the best way to ensure any newly created items get these sorts of global events?
I like to use jQuery, but this is really a conceptual question.
Should I create a function to re-apply any global link effects, or is there a smarter way besides doing it piecemeal?
If using jQuery, you can use the .live() method.
Normally when binding an event handler, the event handler is bound to a specific set of elements. Elements added in the future do not receive the event handler unless it is re-bound.
jQuery's .live() method works around this by binding its own special event handler to the root of the DOM tree (relying on event bubbling). When you click on an element, if it has no event handler directly attached, the event bubbles up the DOM tree. jQuery's special event handler catches the event, looks at its target and executes any user-specified event handlers that were assigned to the target through .live().
Look into jQuery's live function. It will allow you to attach to events when control are created during load, and whenever new ones are created. There is a performance penalty, but it is not significant unless you are loading a lot of elements.
You can use the .live() jQuery method to add listeners to elements that are created after the page is finished loading. Using your example of the exit link (if I understand it correctly):
$(function(){
$('a.exitLink').live('click',function(event){ /* do stuff when a link of class exitLink is clicked */);
});
This will respond to the click event on any link of class exitLink, regardless of when it was created (before or after onload fires).
Hope this helps :)
Yes put simply, where you might have had this before:
$('selector').click(function () {});
Replace it with:
$('selector').live('click', function() {});

Categories