Earlier last week a reader (and forgive me, I wrote down your idea, not your name) asked for a simple demonstration of how to use jQuery to load in a ColdFusion query via Ajax. I whipped up a quick, and hopefully simple, set of demos to help show how easy this is.
First, please remember that there are many ways we can this. Just like ColdFusion provides many ways to skin the cat, so does jQuery. I'm going to demonstrate two main ways as I feel it really shows the two types of Ajax calls most folks will use.
In broad terms, most folks will use Ajax to either load rendered content or pure data. So let's say you want to show a list of people on a page. You want to load this via Ajax. You could have the server return the list, printed out with HTML, line breaks, etc. This is rather simple and is especially useful for times when your formatting needs are complex. It is a heck of a lot easier to format dates in ColdFusion then JavaScript. (Although I bet jQuery has a good date library!)
The alternative is to load pure data. This can be XML or JSON (typically JSON) which is then handled on the client. This requires more work, but typically results in less 'traffic' as you are only sending the data, not data plus formatting.
So which should you use? Whichever works best for you! (Yes, I know, a non-answer. Sorry.) Here are two demos of both in action.
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
function loadQuery() {
$.get('data.cfm',{},function(data){
$("#content").html(data)
})
return false
}
$(document).ready(function() {
$("#loadLink").click(loadQuery)
});
</script>
</head>
<body>
<p>
<a href="" id="loadLink">Load Query</a>
</p>
<div id="content">
</div>
</body>
</html>
In my first example, I have a simple page that consists of one link and one empty div. Notice then loadQuery handles making the Ajax request and publishing it to the empty div. The ColdFusion file handles both creating the query and rendering it (although normally I'd get the query elsewhere, from a CFC for example):
<cfset q = queryNew("person,coolness")>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "person", "Scott Slone")>
<cfset querySetCell(q, "coolness", "100")>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "person", "Scott Stroz")>
<cfset querySetCell(q, "coolness", randRange(1,100))>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "person", "Raymond Camden")>
<cfset querySetCell(q, "coolness", randRange(1,100))>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "person", "Todd Sharp")>
<cfset querySetCell(q, "coolness", randRange(1,100))>
<cfset queryAddRow(q)>
<cfset querySetCell(q, "person", "Scott Pinkston")>
<cfset querySetCell(q, "coolness", randRange(1,100))>
<h1>People</h1>
<cfoutput query="q">
<b>#person#</b> has a cool score of #coolness#.<br/>
</cfoutput>
There isn't anything special about the query, except for a shout out to Scott Slone for feeding my Star Wars addiction via woot.com today!
You can see this in action here: (Removed demo link, see note at bottom)
Now let's look at the alternative. I'll start on the server side first. Here is data2.cfm. Same query, but now we serve it up as JSON and don't perform any formatting:
<cfset q = queryNew("person,coolness")>
... querySetCells cut from code to make it shorter ...
<cfset data = serializeJSON(q)>
<cfcontent type="application/json" reset="true"><cfoutput>#data#</cfoutput>
The front end now needs to get a bit more complex. I've only modified the loadQuery function so I'll just paste in that:
function loadQuery() {
$.getJSON('data2.cfm',{},function(data){
//map columns to names
var columnMap = {}
for (var i = 0; i < data.COLUMNS.length; i++) {
columnMap[data.COLUMNS[i]] = i
}
//begin making my str
var str = '<h1>People</h1>'
for (var i = 0; i < data.DATA.length; i++) {
str += '<b>'+data.DATA[i][columnMap.PERSON]+'</b>'
str += ' has a cool score of '+data.DATA[i][columnMap.COOLNESS]+'<br/>'
}
$("#content").html(str)
})
return false
}
Ok, so the first change is the switch from get to getJSON. This just tells jQuery to go ahead and expect JSON and turn it into a native JavaScript object for me. At that point I wasn't sure what to do. Where do I turn when I want to just... play/test/etc with JavaScript? Firebug. I ran this:
console.dir(data)
So I could look at the result. The result had 2 keys: COLUMNS and DATA. COLUMNS was an array of column names. DATA was an array of data (big surprise there). I realized that the first column in the COLUMNS array matched the first column of data. So if I wanted the person column, I could either hard code the value 0, or, do what I did here, which is to create an object that stored the column name and the corresponding position.
I then create my string. Notice how I make use of columnMap to address the data in the array. Once done, I then simply place the HTML in the div.
You can see this in action here: (Removed demo link, see note at bottom)
Enjoy!
For folks looking for the old code for this, you can download a zip of it here: https://static.raymondcamden.com/enclosures/jquerycfquery.zip
Archived Comments
Timely post Ray... i have a project I am working on and one of the things i need to do is load up the Color's that are available for a product. It will be nice to use jQuery to go query for the Colors and display them on the page.
As Raymond said, there are many ways to accomplish the same task, however if all you want to do is load a chunk of HTML to the browser, you can simplify this function:
function loadQuery() {
$.get('data.cfm',{},function(data){
$("#content").html(data)
})
return false
}
to:
$("#content").load('data.cfm');
The load() method basically handles the AJAX operation and the injection of the response into the DOM for you.
Duh. When I was working on test1.html and got it done, I tried a one line approach, and I _knew_ there was a way, but I was trying to do it with (selector).get, I forgot .load. As I said, duh. Thanks!
Thanks for the post.
If you retrieve that data via a cfc call can you use a mapping in the get or getJSON function's first argument
$.getJSON('/myAppLevelMapping/myPackage/allUsers.cfc',{method : "getUsers"}},function(data)
is the syntax used here correct for referencing the method/path? what does the syntax look like for passing in arguments to the cfc.
Once i can retrieve the data from the cfc one other task I would like to be able to do with the returned data is to return data into the table with proper column headers (change the table's column name from UserName into something like User Name easily. Ideally it would be great to be able to reference another query that stores a reference of a column name to a column display label and as it is looping through the json use the proper display label. (i guess in my query i could write it to say
SELECT UserName as User_Name FROM users WHERE 0=0
and then in my loop i guess i could do a split on the _ as a delimiter.
Any ideas on how to be accomplish this would be awesome. Thanks again for this code it will help out a lot.
Seems kind of related, jCFC was released this morning:
http://jcfc.riaforge.org/
Invoking CFC methods via JavaScript, pretty nice. Although, personally I hate formatting result sets with JavaScript. Isn't there a better way to do this, like using some kind of template or something? Otherwise, the only practical use of returning JSON structures to JavaScript I can see is things like populating select boxes.
I've gone back and forth on the return html vs. data too. In some cases I've found I can have a .cfm page that gets reused: once as an include on the first time viewing and again whenever the ajax call hits it.
One really interesting part of the code sample is that 3 out of 4 of Raymond's friends are named Scott.
There are actually all kinds of things you can do with CFC method call results and jQuery. CFC methods can return generated XHTML just as easily as .cfm files can.
jCFC simplifies all of your jQuery CFC calls to a tight, easy-to-read syntax like this:
$.cfc( 'cfcName', 'method', { arguments }, { callback } );
It handles everything else, including JSON deserialization of the results, invisibly for you.
Instead of a file path to the cfc, you pass it a dot-delimited-path without the .cfc extension, which coincidentally lets you leverage mappings the way that Tim mentioned wanting to.
One really cool thing you can do with jCFC is to design your HTML interface, and then pop out the parts that change (the TR cells of a table of search results for example) into a CFC method and then just make a call like this to have live updating:
onKeyUp = $.cfc( 'cfcName', 'returnTRResults', grabFormValues( ), function( results ){ $.('#table').html( results ); });
@TimB:
1) Yes, I believe that would work, but normally I'd do it in the url, '.../some.cfc?method=getUsers'. Technically method is one more argument, but it is a special argument when calling CFCs.
2) To pass in more args, add more crap to the {} block:
{foo:'ray',zoo:'may'}
etc.
3) Your last item was real confusing to me. If you want to make column names dynamic, well, I guess you could return it. You are allowed to return anything you want in the json, including multiple queries. So you could return a query of your data, and another query for formatting informatio.
@Kit: jQuery has a Template doohicky plugin, but I've not played with it. This is one area where Adobe Spry really shines. I disagree though about your comment about JSON only being good for populating select boxes. JSON is a very good 'data' transport. It is very slim compared to XML or HTML.
@Davide: I assume jCFC uses a proxy CFM on the server to allow you to use mappings?
It sure does.
One other thing I'll mention is that jCFC can do a slew of other things too, like:
Connecting to application-scoped singletons instead of files.
Connecting to application-scoped UDFs.
Configurationless proxy objects, in case you like this syntax:
var myProx = $.cfc( 'User' );
myProxy.backflip( );
It's worth downloading and reading through the documentation PDF which gives you a quick feel for what it can do.
Two corrections:
1. It uses a proxy .cfc not .cfm
2. Typo. Corrected:
var myProxy = $.cfc( 'User' );
myProxy.backflip( );
According to http://www.dynamicdrive.com...
When accessing jQuery dynamically using Google's Ajax API, you must use google.setOnLoadCallback() instead of the venerable jQuery(document).ready() function to execute any code once the document's DOM has become available. That's because jQuery is now being dynamically loaded, and only by using google.setOnLoadCallback() can you reliably know when jQuery has fully loaded (including access to jQuery(document).ready()).
@PSenn: I'm not using google.load. I'm using the script style. It does not require google.setOnLoadCallback().
Perfect timing on the article! I was just trying to figure out how to do this with a login screen. I wanted to update the "loginResponse" div on my page to show "success" or "failure"
Thanks Ray!
Would folks want an example like that? Login form. Click to login. Auth via ajax. If bad, show error. If good, go to new page.
@Ray: Sure, seeing your prototype compared with the way I would have done it, would be a nice, "ahhh that's a better way of doing it" situation for me :)
The other thing was, I was trying to do a modal login screen with nyromodal plugin for jQuery. However, I couldn't get the modal to not go to the next page. I wanted the login form to stay modal. So I gave up (due to time) and went to the normal HTML route.
Did you check out jQuery UI for their modal window? I've got a blog entry on it.
Ray,
All samples, all the time. I am just getting into jQuery and this post couldn't have come at a better time.
JQuery... hmm. Why do I need JQuery at all for doing simple little Ajax stuff when my existing 5 year old JavaScript routines for doing Ajax calls works fine...?
Why use any framework, Fred? Typically because the shortcuts are... well, exactly that, a shortcut. As I said in the presentation I gave last week, Ajax isn't anything new per se. It's 1999 technology. But anything that makes it easier to use is a good thing in my book. Also, jQuery does a heck of a lot more than just Ajax.
@Ray: That's great. Didn't know that was there until now. Thanks. However...(didn't you see that coming?), the modal with form shows how to pull in a form that's embedded within the page. Do know of a way to pull in a cfm/html page instead of embedding it within the page?
Also, I'm finding that I have to click the close button (x) 2x to get the dialog and modal to disappear. First click seems to remove a layer of modality, 2nd click removes everything. I'm using FF 3.0.7. Anyone else seeing this? This happens with all modals on my browser.
A quick and elegant way to substitute CF's cfdiv that adds more to the requests. This post just hit right at the time!
I shoved off couple requests of 50KB after I found out this cool post. Thanks Ray!
I've created a small .ajax() routine just like Ray's but with a login form.
http://www.christopherchin....
Pretty rudimentary, but you get the gist of it. Comments are appreciated. I'll be blogging about this soon.
jCFC looks very interesting. I was just wondering if it could be used with ColdFusion 7? What features of ColdFusion 8 does it require?
Thanks
@Kit, Ray,
I use Ben's templates "plugin" for generating html content from JSON. It works really great. I'm not sure if he's released it anywhere, but the code is available on his site:
http://www.bennadel.com/blo...
Shoot, Chris, how am I supposed to do a blog post on it now? ;)
Ray,
I still think a documentation of a login form would be useful.
I also think an example with the jqGrid to display the returned data would be awesome (especially if text fields can be bound to the selected value from the grid)
for viewing your returned data in a familiar format, this can be helpful:
http://www.netgrow.com.au/f...
another thing that might be useful if you're debugging ajax calls is jquery's $.ajax error option. $.get and $.post don't return the error text, but using $.ajax, you can view the cf error page with this:
$.ajax({
type : 'GET'
,url : 'data2.cfm'
,success: function( data ){
dump( data );
}
,error : function( event, textStatus, errorThrown ){
$('<div/>').html( event.responseText.split(/<\/?body>/)[1] ).prependTo('body');
}
});
I played around with the ajax callback functions a while back. Here's some code I wrote to see what all it was doing:
I was inspired by Learning jQuery pg. 129.
(function($) {
$().ajaxStart(function() {
$('#busy').slideDown();
});
$().ajaxSend(function(myEvent, request, settings) {
$('#debug').append(myEvent.type+'<br />');
$('#debug').append('readyState:'+request.readyState+'<br />');
$('#debug').append('multipart:'+request.multipart+'<br />');
$('#debug').append('type:'+settings.type+'<br />');
$('#debug').append('url:'+settings.url+'<br />');
});
$().ajaxSuccess(function(myEvent, request, settings) {
$('#debug').append(myEvent.type+'<br />');
$('#debug').append('readyState:'+request.readyState+'<br />');
$('#debug').append('status:'+request.status+'<br />');
$('#debug').append('responseText:'+request.responseText+'<br />');
$('#debug').append('type:'+settings.type+'<br />');
$('#debug').append('url:'+settings.url+'<br />');
});
$().ajaxComplete(function(myEvent, request, settings) {
$('#debug').append(myEvent.type+'<br />');
$('#debug').append('readyState:'+request.readyState+'<br />');
$('#debug').append('multipart:'+request.multipart+'<br />');
$('#debug').append('status:'+request.status+'<br />');
$('#debug').append('type:'+settings.type+'<br />');
$('#debug').append('url:'+settings.url+'<br />');
});
$().ajaxStop(function() {
$('#busy').slideUp("slow");
});
$().ajaxError(function(myEvent, request, settings, thrownError) {
$('#debug').append(myEvent.type+'<br />');
$('#debug').append('readyState:'+request.readyState+'<br />');
$('#debug').append('multipart:'+request.multipart+'<br />');
$('#debug').append('status:'+request.status+'<br />');
$('#debug').append('type:'+settings.type+'<br />');
$('#debug').append('url:'+settings.url+'<br />');
});
})(jQuery);
This thread has generated some great discussion. I originally said something like "jCFC is only good for populating select boxes" - I may have been taken out of context, here. It was just an example. I like coding very object-oriented, so concatenating strings to produce HTML using JavaScript just seems like a hack to me. But that's mostly a matter of coding style, and to each their own.
@Ray - I was going to mention something about Spry being a great framework for formatting AJAX data with templates, but I thought it was a bit of an offshoot in a jQuery thread. In hindsight, it may have been relevant.
@Francois - seeing Ben's utilization of a TEXTAREA for templates is great, but it made me think about loading templates dynamically. I guess that over-complicates things, two AJAX requests for each call. Have you seen anything like this done well before?
@Will Fisk:
I was actually just getting started with CF towards the CF8 beta, so I'm not really sure. My guess would be it uses some 8-only syntax ( ++ maybe? ) and the JSON handling and returnFormat="JSON" functionality, which could be handled inside of the single method of jCFC.cfc in 7. Let me know if you end up getting it work on 7 and I'll update the source.
@Kit:
Well, in a way it does feel very hacky. The primary reason you'd want to concatenate strings to generate HTML is optimization. Generating the interface on the client instead of server, offloading that work to a browser and minimizing your transfer size.
As a note, you can easily author the html normally as you would in a .cfm file and plug it into a cfsavecontent in a cfc method to get the same effect. You can also easily write a single CFC dynamic content distributor method that just takes a URL argument and will cfinclude and return the .cfm files you want and letting you leave them as .cfms while still able to take full advantage of the simplicity and rapid development of jCFC.
Oh, also if certain features of cflock or getMetadata or getComponentMetadata changed from 7 to 8. I think that's probably everything.
Could you give an example of using jQuery and AJAX to save form data?
I can. It is a good idea. I've actually got a few simple examples in a list now for me to write.
The links to the test code are broken. I copied your code from the first example and it wont run in IE. I thought your test files did work in IE when i tried them a while back.
I get:
Line: 13
Character: 11149
Code: 0
Error Message: Object required
A question: how would i execute the code when a user changes a select form object instead of a click like in your example.
I moved servers and probably forgot to move the folder. Will do so later today.
I fixed the demo Chad.
I've started a series for beginners to jQuery at halhelms.com/blog. It will concentrate on how to use jQuery with ColdFusion and may be helpful for newbies.
@David McGuigan, the JCFC entry at RiaForge doesn't exist anymore. Have you pulled it down? Also your home page (linked via your comments here) says it's "gone private - au revour". Is JCFC dead or has it been moved elsewhere? Please advise. Thanks.
Hi Ray,
On the top example, once the CFM page results are placed into the div tag is it part of the page and you can call more jQuery against it's contents?
For example I want to make a page that a user selects a product via a drop down box.
I run jquery to go get all of that products colors and place them in the color picker DIV.
Now i want the user to choose a color and hide the color picker DIV and show what color the user picked in another div.
Then add the item to their shopping cart.
Doable?
I was looking for jCFC today as well, curious to see it. As noted elsewhere, it's not on RIAforge anymore. What's up with that?
The developer asked me (fyi, I manage RIAForge, for folks who may not know) to remove it. He had his reasons, but I'll let him speak up if he wants.
@chad: Yes, the content is injected into the DOM and is 'reachable' by normal means.
How would I set this up so I am refreshing query results every 2 seconds via jquery?
JavaScript provides a few different ways to do things in intervals.
window.setTimeout
window.setInterval
window.requestAnimationFrame
Most likely you want setTimeout. You can't use an interval because one ajax request may be slowed down do to network conditions.
Thanks for getting back to me. What element on the page should I apply this to? A DIV with the cfquery inside?
I don't think it matters. You don't apply an interval to a div. An interval is just that - a timed called. Your logic, the one run at the particular time, THAT would update a div.
Any chance of you posting a small example of this? I'm having trouble visualizing how it would work.
Which portion exactly? I mean, given it is easy to do Aajx w/ jQuery
$.get(some ul, function() {
//handle result here
});
The general way of doing this would be:
function doAjax() {
$.get(some url, handler);
};
function handler(res) {
//im the code that handles the result
//when done, request a new ajax call in 2 seconds
window.setTimeout(doAjax, 2000);
});
//fire immediately
doAjax();
Does that logic make sense?
Ahhh I see now. Thanks! That makes sense.
Thanks Ray!
I rarely use anonymous functions any more. I think you posted something a while back that expanded out the syntax like the one above and it really helped me.
Naming is really hard in programming, and I consider the naming of functions self-documentation. By extracting the 2 handlers (which I call "done" and "fail"), then it more plainly shows the nature of the async operation. Speaking of naming, I came across a post where someone called the variable coming back from the call as the "response" variable. Wow that really helped me too! I always say "return result" but in the calling routine, it would be function done(response). It's just a little naming convention that has really helped me keep thing straight.
One more question. I have this working now and refreshing every 2 seconds. It is returning the query data that I want. Does this only return output data or can it return form fields?
What I need to do is set the value of a cfselect option to an id number i am returning from the jquery refresh. I am trying to either bring the whole cfselect back or just the option tag with the id value and nothing seems to happen.
When I change my data.cfm page to just output the id number that is returned.
Is there something different you have to do to return something like this via the jquery examples in this thread?
<option value="#id#">#name_last# #name_first#</option>
"Does this only return output data or can it return form fields?"
I'm not quite sure I understand what you mean. When you make an Ajax request, you can get anything back really. It can be JSON data. It can be HTML - and that HTML could include form fields.
"What I need to do is set the value of a cfselect option to an id number i am returning from the jquery refresh. I am trying to either bring the whole cfselect back or just the option tag with the id value and nothing seems to happen. "
Oh, well if the drop down is already rendered, ie visible in the browser, all you really need back is the ID. Then you use JS to set the appropriate item as selected. I recommend against cfselect and would just use a regular select tag.
Thanks for the help Ray. Here is how I have it set up:
I have a query that is only ever going to return either 1 record or zero results. Currently I have a cfselect that displays the results if any with a value of an ID number. I then have 10 other cfinputs that bind to the option value which is a unique ID number.
I am trying to figure out the best way to auto refresh that select option every couple of seconds so that when it sees a query result it updates the ID number value then all of the other cfinputs would bind and update thier info.
Does that make sense?
So I won't really need to set anything as selected since the recordcount will always be 1 or 0.
Any ideas?
I dont quite understand why you are using a cfselect then. If the idea is to put nothing in it, or one ID, it should be a text input, not a drop down.
Yes I see your point. It could be a text input.
So I guess what I am wondering is if I am binding 10 cfinputs to one cfinput that is a driver input with a value of #ID# what is the best way to refresh the value of that driver input so that when there is a query recordcount update the ID value which in turn would update the other fields through the binds.
What I am doing is scanning a drivers license and OCRing the text fields/capturing the image. I am inserting all of that info into a DB table. So essentially when the scan happens I want all of that data to be displayed on the screen. Once the user fills out the rest of the fields and submits the record it will go back to an entry page with blank fields until the next scan. I have it all working how I want except for the 2 second refresh waiting for the next scan.
Well your service could return 0 when there is no update and X where there is an ID, X beig the ID. The client side code would handle it appropriately.
Again though - I recommend against using cfinput.
Thanks Ray. I'll play around with this a bit and see if I can work something out. I've already switched over to inputs by the way. I seem to be having better results than with cfinputs.