Solution: Scripts not working after Ajax replaces part of page
Copy link to clipboard
Copied
I'm far from an expert at js and jQuery, so I welcome collaboration and corrections on this solution. This is all from the context of a catalog's large product view when using ajax to switch between grouped products.
First, the issue:
- BC updates the product information via an ajax call when you click the 'add to cart' button or make selections between product groupings.
- When BC does this, it entirely reloads page_content.html. The net result: all the script bindings that were attached to those tags are wiped out.
- The scripts are never rebound to the tags because that initially occurs when the DOM is being created. Content changes via an ajax call don't recreate the DOM.
Possible solutions:
- Hardcode the affected scripts inside page_content.html. Don't just link a script file to it. It has to be hardcoded. This is not recommended, as it is bad coding practice to place scripts inline with our html.
- Avoid the use of scripts in that area (good luck
).
- Use delegation with .on() to rebind. I believe this is the best option, and it's the one I am going to demonstrate.
To avoid inline scripts, I like using an external script.js file. It keeps my document clean from script clutter and it's easier to manage my scripts. Also, I want a solution I can implement one time, in one place, instead of with every situation I encounter. Below is an example of how my scripts are organized. The areas in bold are the specific solution.
jQuery.noConflict();
function scriptlist() {
var $ = jQuery;
var Engine = {
utils : {
functionName : function(){
// Do stuff in here
},
functionName2 : function(){
// do something else
}
},
ui : {
functionName : function(){
// Do stuff in here
},
functionName2 : function(){
// do something else
}
}
};
if ( $(element).length ) { Engine.utils.functionName(); }
if ( $(element2).length ) { Engine.utils.functionName2(); }
if ( $(element3).length ) { Engine.ui.functionName(); }
if ( $(element4).length ) { Engine.ui.functionName2(); }
};
jQuery('body').on('click.productSubmitInput', function(){
jQuery.ready(scriptlist());
});
scriptlist();
The key here was the use of the .on() jQuery method. Here is how it works:
- I attached on() to the body element on the page with jQuery('body').on()
- It's attached to the body, because the body is outside of page_content.html and won't have it's binding removed during the ajax call. Events register all the way up their ancester elements on the page, so on() can pick up the event all the way up to the body.
- Told it to look for a certain event: 'click.productSubmitInput'
- When the event occurs, it should trigger ready() to refresh the DOM. jQuery.ready()
- and run my functions against it, thereby rebinding my document.
Copy link to clipboard
Copied
If anyone has thoughts about this, I would love to hear them. I'm always happy to stand corrected or receive new insights! I think it would be nice to have a 'best practice' model collaborated on by the community.
Copy link to clipboard
Copied
an .on on the body is really nasty Adam. You nto see why?
Google , heaps of articles out there on how you do it the right way Adam.
Copy link to clipboard
Copied
I'll check it out. I'd really love to hear/see better solutions people would implement instead. Even if it's just a slight change. How would you do it differently, Liam?
Copy link to clipboard
Copied
Really fun to google and read articles.
Also side note. I hope people test IE when they do partial refresh stuff, they may notice something if they do not know what IE does
When you Ajax Adam, basically you have a callback element to it. It is there for a reason
Copy link to clipboard
Copied
I think I will attach it to 'document' because it's faster. I don't want to attach it to something deeper because everybody's implementations are different. Not designer will have the same page structure I do. I suppose I could simply note in the doc that the shorter distance it goes the better.
Copy link to clipboard
Copied
Here's a quote from api.jquery.com:
Attaching many delegated event handlers near the top of the document tree can degrade performance. Each time the event occurs, jQuery must compare all selectors of all attached events of that type to every element in the path from the event target up to the top of the document. For best performance, attach delegated events at a document location as close as possible to the target elements. Avoid excessive use of document
or document.body
for delegated events on large documents.
Document is faster than body, but the main issue still remains. It's likely doing a lot more work than it needs to. So, for implementing this, find an element just outside the affected area to attach it to.
Copy link to clipboard
Copied
You really need to google, no where near faster, bit cringe worthy actually I do not think you understood what I said.
Copy link to clipboard
Copied
Liam, can I convert this into a document. I intended for it to be editable.
Copy link to clipboard
Copied
What happens when you run a script, when you want it, not constant as you shown here. Google, honestly. You wont see ajax re-iniitlsement done in this way. I really would never do it like this ever.
Copy link to clipboard
Copied
I will do try find time to do a demo with your method and one of the correct ways and get you to run it in chrome and actually see how much more time your method has and load on it.
Copy link to clipboard
Copied
Sounds good.
Believe me, I Google. The thing about that is, though, that you have to know what you are looking for. I'm nowhere near your level, so the questions I would ask are not the same as the questions you would ask.
Copy link to clipboard
Copied
You will get double launches on some instances if you do that as well (done similar you see myself, trying to be clever and not research).
Look at the jQuery ajax.
On success as a function you can do stuff. Your code is all objects, what do you need to trigger, what are you actually fetching?
When to use .load, .ajax, .post .get etc. What are you fetching and bringing to the table.
Also you have IE. IE Even in the latest one (but does not generate the issue mind you) heavily caches. MS cheat the speed they have by heavy heavy cache.
You use ajax to refresh an image on a page it will work in all but IE. IE cached it, wont let it go. Even does it with content elements that contain images and other cases.
Think about what you done, Everything always on. Do that with a light switch what happens?
Copy link to clipboard
Copied
So are you saying to put it on the individual functions so the lights are only on in the current room? Save electricity?
Copy link to clipboard
Copied
As far as ajax is concerned, I don't have access to BC's ajax request. But now you have me wondering if I can call functions based on success of their request. I know jquery has ajax events for...ajaxSuccess()?
Copy link to clipboard
Copied
jquery Ajax can set the response as text, html json etc depending on what is returned. You can have html returned and handle it.
But there is always a success and fail. I see heaps of people on BC using ajax and have a blind success and no correct handling.
Take a bc confirmation page from something which is a success or fail. Both those in terms of ajax is a success so you handle that on top.
But a sucess function is something you run anything in.
If a function or object of yours ends due to an ajax load you only need to relaunch that, not everything or have everything live.
You also need to consider that inline scripting in some calls will actually run. IF you have html response and just dump it in a element it will execute and screw up.
Your method will also run into endless loops as wll in some cases.
http://api.jquery.com/jQuery.ajax/
http://jqfundamentals.com/chapter/ajax-deferreds - Read the asynchronous bit
Copy link to clipboard
Copied
@Liam,
I just got to a place where I could look through the links you provided and really study is out. They led me to an important paragraph on API.jquery.com:
jQuery.Deferred() introduces several enhancements to the way callbacks are managed and invoked. In particular, jQuery.Deferred() provides flexible ways to provide multiple callbacks, and these callbacks can be invoked regardless of whether the original callback dispatch has already occurred. jQuery Deferred is based on the CommonJS Promises/A design.
Second to last sentence is the key. Instead of using .on() to recall scriptlist(), use .when().then() or something. Then, for each function inside scriptlist, make sure it is set to be conditional so I'm not running more code than necessary.
Copy link to clipboard
Copied
Your getting there Adam
Copy link to clipboard
Copied
Sweet! That took a lot. I suppose the only other BIG thing to take care of (as far as efficiency is concerned) has to do with calling the Dom a lot. The whole Dom.js practice you were referring to in the other discussion.
Copy link to clipboard
Copied
Hey Liam,
I would love the ability to create and edit a document on here. That's what I originally had in mind for this. What's the word on that.
Copy link to clipboard
Copied
Is there an example for using jQuery.Deferred() to hook into ajax callback?
Copy link to clipboard
Copied
function executeCallback(callback,param){
if (typeof callback === 'function') {
var deferred = $.Deferred();
if (param) deferred.resolve(callback(param));
else deferred.resolve(callback());
return deferred.promise();
}
}
$.ajax({
type: 'POST',
url: 'my/action/path',
data: $('#myForm').serialize(),
success: function(response) {
executeCallback(myScript,response);
}
});
Copy link to clipboard
Copied
Just for future people you might want to update the original post as you figured out that the entire page isn't refreshed just the container DIV. You have the correct methods with rebinding everything on content reload.
Copy link to clipboard
Copied
Wish I could. I can't edit the original post.
Copy link to clipboard
Copied
Hi Adam,
There seems to be a total disconnect in this thread.
You originally posted "BC updates the product information via an ajax call when you click the 'add to cart' button or make selections between product groupings."
With a BC ajax call and there is no way to access a call back, not that I've found anyway.
The .on() solution works. There may be a slight performance penalty but there are situations where it seems to be unavoidable to make the UI work.
So the problem simply stated is: "BC ajax breaks jQuery bindings and there is no way to rebind without access to a callback."
If there is in fact a way to rebind without .on() or a similar approach I would love to hear about an ACTUAL SOLUTION.
Can we please see a serious discussion of this issue and hear from BC developers?
It is a real problem trying to implement the myriad work arounds necessary to create a quality shopping experience with your hands tied.

-
- 1
- 2