Yesterday a reader wrote me with an interesting question. He was using ColdFusion's Ajax controls to load content into a cfdiv. Within the content loaded via Ajax, he had a simple form with some checkboxes. He wanted to use jQuery to support a "Check All" button. For some reason though the event handler he used never worked. Let's take a look at his initial code.
First - here is the root page:
<cflayout type="border" name="layoutborder" height="200">
<cflayoutarea name="Center" position="center">
<a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
</cflayoutarea>
</cflayout>
<cfdiv id="catz"></cfdiv>
As you can see, he uses a simple border style layout with a link that makes use of ColdFusion.navigate. Now let's look at test2a.cfm:
<script>
$(document).ready(function() {
console.log("Confirm I ran...")
$("#checkboxall").click(function() {
console.log("Ran the checkbox")
var checked_status = this.checked;
$("input[name=mapid]").each(function()
{
this.checked = checked_status;
});
})
})
</script>
<input id="checkboxall" type="checkbox" />Check all below<br/>
<FORM>
<input name="mapid" type="checkbox" /><BR>
<input name="mapid" type="checkbox" /><BR>
<input name="mapid" type="checkbox" /><BR>
</form>
Pardon the tabbing above - these scripts went through some adjustments. Anyway - nothing too special here. You can see the main form with the special checkbox above it. The jQuery code has an event listener for the special checkbox. When run it simply gets the other checkboxes and either checks or de-checks (is that a word?) the boxes.
So again - this should work. On a whim, I made a tweak. I switched from using a click listener to the live() feature. I've blogged on this before. jQuery's live() feature allows you to use event listener for DOM items that don't exist yet. So if you want to always bind to links, and you load content that may contain links, you need to use live(). From what I knew though, this shouldn't be required. The event listener was run when the content was loaded. However, when I switched the code to:
$("#checkboxall").live("click",function() {
It worked! So that was last night. This morning I tried to dig a bit deeper into this. As I said, this change should not have been required, but it obviously worked (the reader confirmed it). So I did an interesting test. I wrote a new front end page that made use of both ColdFusion.navigate and jQuery:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#loadIt").click(function() {
$("#content").load("test2a.cfm")
})
})
</script>
<input type="button" value="Load Content" id="loadIt">
<div id="content"></div>
<cflayout type="border" name="layoutborder" height="200">
<cflayoutarea name="Center" position="center">
<a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
</cflayoutarea>
</cflayout>
<cfdiv id="catz"></cfdiv>
And here is where things get crazy. As you can see, I've got the original code on the bottom. Above it I have a simple button that runs the jQuery load() function. Basically though both should be doing the same thing: Request test2a.cfm and load it into a thing.
I was shocked to see though that the jQuery loaded content worked as I expected! At this point I was truly lost. I opened up Firefox (I've pretty much given up on Firefox except for Firebug) and looked at the XHR requests. In general, I saw two things different.
First - ColdFusion's navigate function adds a bunch of junk to the URL. I knew this of course. It handles suppressing debugging, preventing caching, and other stuff. As an example, here is the jQuery URL versus ColdFusion's:
http://localhost/test2a.cfm
http://localhost/test2a.cfm_cf_containerId=catz&_cf_nodebug=true&_cf_nocache=true&_cf_clientid=730C445ABFA5719A14A7DBC87B0E5259&_cf_rc=0
As I said - I expected that. The second change was surprising. In a previous blog post I talked about how you can check for a HTTP header, X-Requested-With, to sniff an Ajax request. This is set by jQuery and other Ajax frameworks. It isn't (as far as I know) an actual part of the low level HTTP implementation in JavaScript. ColdFusion's code, however, does not send this header. While I don't believe this is the issue, it is surprising. I'm going to file an ER for this and I'm going to see if I can tweak the core ColdFusion JavaScript code to add this header in. Maybe it will make a difference.
Archived Comments
It does not make sense that the lack of the HTTP header for the request would affect how the browser handles the response.
Is it possible that some of the built in CF AJAX stuff is somehow mucking with the response?
I can't imagine why though. Adobe would have to go out of their way to break this (as far as I know).
For what it's worth I have abandoned cfdiv in favor of using the div/load combo setup with jQuery. In limited testing, using jQuery's load is way faster and in my opinion, cleaner than my old fav (navigate).
I'm working on a blog post that combines a fairly complex shipping app that uses a multi-pannel layout (using nested cflayout tags) but changes and updates content using jQuery load rather than navigate. The difference in speed and code simplicity with the jQuery version is notable. All of my CF apps pretty much make use of cfgrid, cfajaxproxy and a couple of other nice to have CF AJAX features - the rest is all jQuery.
After working with CF's AJAX framework for the last two years I was initially very biased towards doing anything that was not contained within the application server out of the box. However, I'm come to believe that jQuery is different - it's well documented and maintained and it doesn't seem to be losing steam or getting stale like lots of other AJAX frameworks that have already bit the dust. (EXT is pretty cool to but certain things are made harder than they need to be)
Why did I resist jQuery for so long? Perhaps I'm dumber than I had originally thought!
@Ray - maybe there is some built-in event handler for CF AJAX requests that we do not see on the surface?
@Scott,
Why not use navigate's optional callback/error handler arguments with custom JavaScript functions to root out the problem? (I've had success doing this in the past)...
ColdFusion.navigate(URL [, container, callbackhandler, errorhandler, httpMethod, formId])
@Andy - My guess would be that if there is a built-in event handler (which there almost has to be, how else would the results of navigate() be populated into a div?), it would get called prior to any custom callbacks, etc.
Hey Ray. What'cha got against Firefox?!
-Brian
It's not Chrome. ;) Seriously though - Chrome is just zippier for me.
Have you tried using ajaxonload on test2a.cfm?
Why? That really isn't relevant to the problem here - unless I'm missing something.
Sorry, i've read quickly (and badly at this point) the post and it seemed only a problem about executing javascript in a page loaded by Coldfusion using XHR thus i suggested the use of ajaxonload instead of the $(document).ready.
I
I ended up abandoning cfdiv awhile back and switching to a jquery div like andy said. I ran into all kinds of weird behaviors off and on mixing cfdiv and jquery.
@DanaK
jQuery reminds me of Nuprin...
Little, Yellow, Different, Better.
I know that this was a week or so ago but it happened to me again using cflayout, load and jQuery. After much work it seems that the simplest solution is always the best...
Put this function into the Parent page (the one with the cflayout container)...
toggleCustomerFilters = function(filterVal){
var selected = filterVal;
$("input[type=checkbox]").each(function(){
if (this.name != 'toggleFilters'){
this.checked = filterVal;
}
//console.log(this.name);
});
}
Then just add this to the loaded page...
<input type="checkbox" name="toggleFilters" onclick="toggleCustomerFilters(this.checked)" checked="checked"/> Select All
The only thing different up above was that I used a pseudo class on the checkboxes rather than the named checked (which is what Ray's guy was doing). This probably had no outcome but who knows. Another thing is the sparsely documented shortcoming that I've come to rely upon as much as windows users rely upon Ctrl+Alt+Delete...
If your ColdFusion code is not playing nice and you are using ANY sort of AJAX on the page then structure your function as...
myFunctionName = function()
rather than
function = myFunctionName()
And yes, it is not slick and I'm ready to be smote for not doing it the one true way, but it works and even your little sister and Wesley Crusher the acting Enterprise Steerer Around Guy can understand it.
I know this is an old post but I wanted to throw in my thoughts. I'm really new to jQ so I may be way off.
In my experience here is what is going on with the ColdFusion.navigate. When using ColdFusion.navigate to load the contents into the container, the DOM is already ready. Thus the $(document).ready() in test2a.cfm will fire immediately as the script is loaded into the container. $("#content") will not exist at that point therefore failing to add the listener to the element. This also explains why changing to live fixes it. Live "monitors" the DOM for changes then adds the listener once it is there.
The solution I have used is the ajaxonload(). Use a regular named function in place of $(document).ready() and then put a <cfset AjaxOnLoad(functionName)>. I have also used <cfset AjaxOnLoad(function(){foo(1,2,3);bar(4,5,6);})> when I wanted to include parameters with the ajaxonload or do more than one thing.
I am experiencing an issue with cfdiv that seems to be related to this issue. I am trying to get tablesorter (http://tablesorter.com/docs/) to work within a cfdiv. I can get it to work when not in cfdiv. I can get jQuery to alert though, so jQuery is working. I think this is related to the (document).ready or onajaxload. Any help would be much appreciated.
Easiest recommendation is to stop using cfdiv. If you are already using jQuery, you don't need it. To mimic cfdiv's "load a url into this div", you can simply do this in jQuery:
$("#someid for a div").get("theurl.cfm", {}, function(res) {
//optional call back handler;
});