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:1 <cflayout type="border" name="layoutborder" height="200">
2 <cflayoutarea name="Center" position="center">
3 <a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
4 </cflayoutarea>
5 </cflayout>
6 <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:
2 <cflayoutarea name="Center" position="center">
3 <a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
4 </cflayoutarea>
5 </cflayout>
6 <cfdiv id="catz"></cfdiv>
1 <script>
2 $(document).ready(function() {
3 console.log("Confirm I ran...")
4 $("#checkboxall").click(function() {
5 console.log("Ran the checkbox")
6 var checked_status = this.checked;
7 $("input[name=mapid]").each(function()
8 {
9 this.checked = checked_status;
10 });
11
12 })
13
14 })
15 </script>
16
17 <input id="checkboxall" type="checkbox" />Check all below<br/>
18 <FORM>
19 <input name="mapid" type="checkbox" /><BR>
20 <input name="mapid" type="checkbox" /><BR>
21 <input name="mapid" type="checkbox" /><BR>
22 </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:
2 $(document).ready(function() {
3 console.log("Confirm I ran...")
4 $("#checkboxall").click(function() {
5 console.log("Ran the checkbox")
6 var checked_status = this.checked;
7 $("input[name=mapid]").each(function()
8 {
9 this.checked = checked_status;
10 });
11
12 })
13
14 })
15 </script>
16
17 <input id="checkboxall" type="checkbox" />Check all below<br/>
18 <FORM>
19 <input name="mapid" type="checkbox" /><BR>
20 <input name="mapid" type="checkbox" /><BR>
21 <input name="mapid" type="checkbox" /><BR>
22 </form>
1 $("#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:
1 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
2 <script>
3
4 $(document).ready(function() {
5
6 $("#loadIt").click(function() {
7 $("#content").load("test2a.cfm")
8 })
9
10 })
11
12 </script>
13
14 <input type="button" value="Load Content" id="loadIt">
15
16 <div id="content"></div>
17
18 <cflayout type="border" name="layoutborder" height="200">
19 <cflayoutarea name="Center" position="center">
20 <a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
21 </cflayoutarea>
22 </cflayout>
23 <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.cfm2 <script>
3
4 $(document).ready(function() {
5
6 $("#loadIt").click(function() {
7 $("#content").load("test2a.cfm")
8 })
9
10 })
11
12 </script>
13
14 <input type="button" value="Load Content" id="loadIt">
15
16 <div id="content"></div>
17
18 <cflayout type="border" name="layoutborder" height="200">
19 <cflayoutarea name="Center" position="center">
20 <a href="javascript:ColdFusion.navigate('test2a.cfm','catz')">Click Here</a>
21 </cflayoutarea>
22 </cflayout>
23 <cfdiv id="catz"></cfdiv>
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.
Comment 1 written by Scott Stroz on 8 January 2010, at 9:51 AM
Is it possible that some of the built in CF AJAX stuff is somehow mucking with the response?
Comment 2 written by Raymond Camden on 8 January 2010, at 10:07 AM
Comment 3 written by Andy Sandefer on 8 January 2010, at 10:08 AM
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!
Comment 4 written by Scott Stroz on 8 January 2010, at 10:11 AM
Comment 5 written by Andy Sandefer on 8 January 2010, at 10:18 AM
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])
Comment 6 written by Scott Stroz on 8 January 2010, at 11:36 AM
Comment 7 written by Brian H. on 8 January 2010, at 4:58 PM
-Brian
Comment 8 written by Raymond Camden on 8 January 2010, at 5:14 PM
Comment 9 written by Andrea Veggiani on 10 January 2010, at 11:27 AM
Comment 10 written by Raymond Camden on 10 January 2010, at 1:41 PM
Comment 11 written by Andrea Veggiani on 11 January 2010, at 9:45 AM
I
Comment 12 written by DanaK on 12 January 2010, at 10:28 AM
Comment 13 written by Andy Sandefer on 12 January 2010, at 11:23 AM
jQuery reminds me of Nuprin...
Little, Yellow, Different, Better.
Comment 14 written by Andy Sandefer on 18 January 2010, at 12:20 PM
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.
[Add Comment] [Subscribe to Comments]