jQuery scripts on ajax content WITHOUT using $(document).on - javascript

I am using an infinite scroll plugin which uses ajax.
When the 'next page' is loaded via ajax, all other ajax related scripts that are on the next page do not work. I have been told that I have to use 'delegated events'(ie change $(id).click() to $(document).on) - problem is that means editing multiple plugins and changing dozens of function calls.
Is there any way I can avoid changing everything to $(document).on and do something cool with the infinite scroll?????
I'd much rather modify the infinite scroll plugin rather than modifying other ajax related plugins to make them fit.

Unfortunately you have very few options here, and switching to delegated events is by far the best of them.
The problem is that your old code was assigning behaviour to "particular elements" when what it should really have been doing is creating page-wide responses to "certain types of actions".
I see 3 possibilities, and only one of them is guaranteed to work.
Run any scripts that are needed on new pages each time a new page is loaded. The downside here being that unless you are careful about also "tearing down" between content loads you will have behaviours repeating or colliding with each other (eg: double popups, broken animations).
Encapsulate the dynamic areas in <iframe>s. Depending on your architecture this may or may not be possible, and certainly won't be easy to integrate with some kind of infinite scrolling plugin which already expects a certain page structure.
Bite the bullet and fix the crappy code.

Loading scripts inside your ajax loaded content is a bad way to start with anyway. What you need is event delegation to attach itself to any dynamically added elements.
$("body").on("click", ".yourclass", function() {
//This function will run for every element with `yourclass` class you load via ajax
});

If you must keep using .click() then you must have a function you can call on the new content to re-hook the events every time you add more content to the page.
e: though it is worth noting that a change from .click to .on can often be handled by a properly structured find/replace

Event delegation is the correct solution. The issue is that the HTML elements on the "next page" were not part of the DOM when the page loaded. Therefore, if you did something like:
$(function() {
$('#some-element-on-the-next-page').click(function() {
foo();
});
});
Your handler did not bind.
I wouldn't attach the events to $(document). I would attach them to the closest parent which is available when the DOM loads. For example, the body tag or the fixed width wrapper which is the first child of the body (assuming your layout uses this type of structure.)
Make sure that the element that you attach to is not emptied with .empty() or repopulated with .html() as that will break the binding. Attaching the delegated handlers lower down on the DOM tree will give you better performance since the events will not have to bubble all the way up to the document node to fire your methods.
You shouldn't need to rewrite all of your functions and plugins, just the bindings to the events that fire them.
I typically use the module pattern and de-couple my method definitions from the click handlers. All of my methods are defined in the outer closure. I'll have a "document ready" section where I bind user events like clicks.
For example:
var myModule = (function() {
var public = {};
public.foo = function() {
// do something cool here
};
// document ready
$(function () {
$('#site-container').on('click', '.js-foo', function() {
public.foo();
});
});
return public;
})();
If you need to change the bindings in the future you will only need to change the call inside the document ready section.

Related

jQuery click event handler attached to dom element

I am having hard time while building e-commerce cart module with jquery.
Lets say that if i write a tags in html like this:
<div class="add-to-cart">+</div>
and then target it in my app:
this.$products,
this.$pa,
this.$ip,
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
var self = this;
this.$ip = function() {
var init = function(action, product) {
/.../
};
self.$pa.on('click', function(event) {
event.preventDefault();
init('add', this);
});
};
This method is possible while im displaying products because they are displayed by php on page refresh so i have all the + links generated by php on html.
The problem is on the checkout file, this is the page when i display entire cart filled with products, cart must be generated and handled in jQuery and AJAX.
And code that i showed you doesnt work in cart page beacuse those links are appended for each product via jQuery into the DOM.
I have been study possible methods and there are few, the most in favour is to do this:
$(document).on('click', self.$pa, function(event) {
The problem with that solution is that it also is considered practice to be avoided due to high resources drain, i can see the difference in execution time myselfe, it takes a lot longer on low end devices. Is there some neat trick that can be used or method that is considered good practice to do in that situation?
<--- EDIT (Solution) --->
Instead of calling:
this.$products = $('.shopperProducts'),
this.$pa = this.$products.find('.shopperAdd');
on the beginning, i have to call it after i load elements into DOM and then they became targetable, then i just have to use self.$ip(); and event handlers can be attached. Without using any sort of workarounds, the solution was just to change order of executing commands.
There are two main strategies that you can use for adding click handlers for elements that you dynamically add to the dom.
One, You can add click handlers to the DOM element each time you create one
var addToCartButton = $('<div class="add-to-cart">+</div>');
addToCartButton.on('click', function(){
init('add', this);
};
// then you add your DOM element to the page
$('.container').append(addToCartButton);
Two, you can have a master click event listener on the page listen for all clicks where your buttons fall, and in your click handler, figure out whether the user is clicking on your element or not. This is ultimately more efficient and you don't have to add or remove event handlers each time you add elements to your page. This pattern is called event delegation, and here's another post on Stack that probably explains it better than I can
What is DOM Event delegation?
$('.container').click(function(event){
if ($(event.target).is('.add-to-cart') || $(event.target).parents().is('.add-to-cart')) {
// handle add to cart
}
})
BTW, your use of the self variable doesn't actually do anything, and neither does declaring this.$pa. You're basically accessing the property "$pa" of your this object, but not doing anything it.

Bind Knockoutjs viewmodels across different pages dynamically loaded by jQueryMobile

It is a frequently asked question I am aware however, I realized there were 3 questions always ambiguiosly answered.
Why don't the new pages inherently get bound?
What jQm (jQueryMobile) event should be used to identify and bind the viewmodel?
Why does not it work even after I followed step 1 and step 2
Let me try to answer to the best of my knowledge
Why don't the new pages inherently get bound?
Knockoutjs is not aware of any content loaded after you have bound your viewmodel. Ofcourse any change rendered within the viewmodel is tracked but new bindings cannot be applied to any external content
Note the "external" keyword. jQm offers to maintain multiple pages in the same document using different div with different pageID, all these pages are inherently bound because knockoutjs does not care about the jQm rendering of pages.
What jQm event should be used to identify and bind the viewmodel?
Depending on your requirement use any of the page events to perform this, however I find the load event $('.selector').on('pagecontainerload',function(event,ui){...}); quite useful. Here below is a sample code.
$('body').on('pagecontainerload', function (event, ui) {
ko.applyBindings(viewModel, $('#externalPageID')[0]);
});
Note that externalPageID is not the name of the document but the pageID within the document. Also note that the applyBindings method does not take a selector as the second argument but a javascript object and hence the [0].
Why doesn't it work even after I followed step 1 and step 2
You are not alone, it did not work for me either. Though pagecontainerload event fires after the page is loaded and enhanced by jQm there is a very short lag before the page is accessible by the applyBindings method. I do not know why and I request any suggestions or comments on this. Luckily there is a workaround though, here below is a sample code
$('body').on('pagecontainerload', function (event, ui) {
waitToLoad(ui.page[0].id);
});
ko.applyBindings(viewModel); //bind the initial page
function waitToLoad(pageID) {
if ($('#' + pageID).length > 0) {
ko.applyBindings(viewModel, $('#' + pageID)[0]); //bind the external page
}
else {
setTimeout(function () { waitToLoad(pageID); }, 100);
}
}
The above piece of code binds the initial page immediately and whenever a new page is loaded, it is also bound. But make sure that you do not bind a page which is already bound, the situation is less likely but it could happen if you force reload a page in the DOM. Make sure you handle that manually.

How to avoid multiple loading of jQuery functions?

I am using following code on my page which I am loading in ajax.
$(document).ready(function() {
$('#button_id').click(function() {
//Do Something
});
});
Now When I click on the button action happens multiple times. I know that its happening because I am loading the ajax page multiple times.
Please help me solve this.
You can use .off() to remove existing listeners:
$(function() {
$('#button_id').off('click').click(function() {
//Do Something
});
});
If I am wrong about your implementation I apologize. Your problem may exist because the binding is created on first page load and then on subsequent ajax loads with new scripts being inserted and creating duplicate bindings. You should prevent any bindings from being generated on ajax loads to prevent duplicate bindings unless you are good with cleanup.
If the button you are clicking on exists in the ajax loaded area then you should use delegation to ensure that the click handlers still work.
For example:
$( "body" ).on( "click", "#button_id", function() {
//do something
});
This will add a binding to the body element, but more specifically to the id #button_id. A click event on the button will propagate and bubble up to the body element (or whatever parent element you choose).
This makes it so that dynamic elements can be inserted in the DOM and only one event handler is needed to listen for it.
No need for .on() or .off() calls for individual ajax loads. This allows your bindings to be much cleaner.
Of course, if your button is not likely to exist on the page all the time then it would not be a good idea to keep extra bindings. Only create these types of binding if they are always needed to prevent optimization issues.
A cleaner solution would be to remove that code from the ajax loaded HTML and use one single event handler in the master page
I guess your problem is the event is firing many times.
To fire only once try this:
$(document).ready(function() {
$('#button_id').on("click",function(e) {
e.preventDefault(); // This prevents the default non-js action (very used for anchors without links or hashes)
e.stopPropagation(); // Prevent the bubling of the event and spread more times
//Do Something
});
});
If doesn't work with e.stopPropagation(); try with e.stopInmediatePropagation();
Adding documentation for the last method I suggested. It could solve your problem.
http://api.jquery.com/event.stopimmediatepropagation/

Should I set multiple element bindings on $(document) in single or separate calls?

I'm trying to clean up and improve some code. There is a fairly large snippet which attaches multiple events to $(document) like so:
$(document).on('eventA.nameA', '#pageA', function(){
// do sth
});
$(document).on('eventB.nameB', '#pageB', function(){
// do sth
});
$(document).on('eventC.nameC', '#pageC', function(){
// do sth
});
So basically all of the event bindings go directly on the document.
Question:
Is there a limit (I found about 50 separate doc bindings ) and what's the advantage/disadvantage of setting this on a single selector like so:
$(document)
.on('eventA.nameA', '#pageA', function(){
// do sth
})
.on('eventB.nameB', '#pageB', function(){
// do sth
})
.on('eventC.nameC', '#pageC', function(){
// do sth
});
Most of the bindings are page-specific (it's a Jquery Mobile application, so once a page is pulled into the DOM, its bindings are set like this). So there is really no other place to put these bindings, because will be pulled in and out while $(document) persists.
Thanks for input!
The advantage of having them on a single selector is that you avoid calling $(document) each time (which has a smaller overhead, because $(document) gets cached). The disadvantage is that they all have to be right after one another, and you indent them one more level. It's not really important.
What is important is that if those are close to the real selectors, using delegation on document for ID selectors is probably really wasteful. Just wait until the document's ready, or update the event handlers when the elements are updated - especially if all these elements won't actually exist on the same page at the same time. If the selectors are actually more complex than that, disregard this paragraph :-)

First plugin, how can I make it live for dynamically generated content

http://blog.jeremymartin.name/2008/02/building-your-first-jquery-plugin-that.html
Based on this tutorial, I created my first plugin and made some modifications.
But, the plugin doesn't work for dynamically generated content when I load new content on the site.
I have no idea how to go about making it live. The events are already using .on but the building of the pluggin doesn't work.
Here is the jsFiddle: http://jsfiddle.net/bT4dH/13/
I know I could call the plugin everytime I add content dynamically, but as that gets messy very quickly, I would prefer to avoid it, and integrate it directly into the plugin.
I was starting to try this:
$(document).on('DOMNodeInserted', function (e) {
if (e.target.classname === 'JS_postShrinker') {
$(e.target).shrinker(options);
}
});
But it doesn't work
UPDATE
Please ignore the DOMNodeInserted, I was just trying it. How do people in general make plugins live? I have the same problem with:
http://code.google.com/p/jquery-watermark/
http://unwrongest.com/projects/elastic/
And many more...
You can simply make it live by using .on() in a bit different way, with delegation. The documentation explains it in detail.
$(closeParent).on(event, selectorForElement, handler);
All you have to do is to choose a parent that you surely know will be part of the DOM when you attach the handler. In the worst case, you can choose body.
Then you attach the handler on this element, and specify the selector for the child elements (that can be inserted into the DOM at any time). The handler will run whenever the event is fired on any of the child elements matching the selector. This method makes use of event bubbling in a very clever way.
EDIT: Of course this will only work for event handling. If you need to initialize your elements when they are added to the DOM (resizing, decorating, etc.), you better call the plugin every time this happens. This is how most plugins work. The DOMNodeInserted event is not cross-browser and deprecated, like all Mutation events, so you should not use it.
There is an extra parameter (selector) to delegate the event on it:
$(document).on('DOMNodeInserted',"." + opts.clickZoneClass, function (e) {
if (e.target.classname === 'JS_postShrinker') {
$(e.target).shrinker(options);
}
});

Categories