Easy AJAX using ColdFusion, jQuery and CFCs
A recent post on CF-Talk asked whether ColdFusion could use AJAX to do a database lookup. This is actually extremely easy to do in ColdFusion 8+, because it natively supports returning data in the JSON format.
To show how easy this is to do, I decided to throw together a little demo. This took me about 10 minutes to write—most of which was writing the markup. However, the bulk of the work is going to be handled automatically by ColdFusion, which will handle converting your data to JSON and by the jQuery Field Plug-in (which I wrote) which will handle populating your form from the data it receives from ColdFusion.
To show just how easy this all is, here's the jQuery code required to make an AJAX call to a CFC:
$.ajax({ // the location of the CFC to run url: "example.cfc" // send a GET HTTP operation , type: "get" // tell jQuery we're getting JSON back , dataType: "json" // send the data to the CFC , data: { // the method in the CFC to run method: "getUserById" /* send other arguments to CFC */ // send the ID entered by the user , userId: $("#userId").val() } // this gets the data returned on success , success: function (data){ // this uses the "jquery.field.min.js" library to easily populate your form with the data from the server $("#frmMain").formHash(data); } // this runs if an error , error: function (xhr, textStatus, errorThrown){ // show error alert(errorThrown); } });
Our CFC looks like this:
<cfcomponent output="false"> <cffunction name="getUserById" access="remote" returnType="struct" returnFormat="json" output="false"> <cfargument name="userId" type="numeric" required="false" /> <cfset var user = structNew() /> <!---// The only tricky part here is to use the bracketed notation to match the case in your HTML, JS is case sensentive. If you use the dot notation (user.name) then the keys will be returned in uppercase. //---> <cfset user["name"] = "User " & arguments.userId /> <cfset user["email"] = "user_" & arguments.userId & "@example.com" /> <cfif (arguments.userId mod 2) eq 0> <cfset user["gender"] = "f" /> <cfelse> <cfset user["gender"] = "m" /> </cfif> <cfreturn user /> </cffunction> </cfcomponent>
I've posted a working example so that you can see how this code looks. For the "User ID" just enter any number. If an error occurs, the error callback will handle displaying the error to the screen. I've also posted the source code as a zip file you can download.
NOTE:If you're still on ColdFusion MX & 7, there's a little more work because non-string data automatically gets converted to WDDX. While there are JS libraries for converting WDDX to native JS objects, they can be hard to find now that the OpenWDDX site has been shut down. You can find tools at RIAForge though that can convert your data into a JS string--which jQuery will automatically evaluate when it's received.
UPDATE:I've added some sample code to the download to show how you can use a proxy template in you're using ColdFusion 7 to get the same results. Just look at the example_cf7.cfm. The only difference is we call the example_cf7_proxy.cfm template instead of calling the example.cfc directly. The proxy template uses CFJSON to convert the results to JSON.
[UPDATED: Friday, March 04, 2011 at 9:59:11 AM]
Comments
Ray had a blog post about it a while back.
I was trying to simply the code as much as possible to just the key parts. It's pretty easy to convert a query row into a struct to return instead.
I prefer using the "url" inside the settings object for the AJAX call--I think it's clearer. Plus, I usually end up w/a base AJAX object that includes all my defaults and then extend it w/just the data I need to pass for each AJAX operation.
I used the "get" operation just so it would be easier to open up w/the output in a new tab if you wanted to see the output being generated. I thought it might be easier for an example in case someone is having issues debugging the example code.
Code is below:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitiona...;
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>AJAX Proxy JSON Test</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1...;
<script type="text/javascript">
$.ajax({
// the location of the CFC to run
url: "user.cfc"
// send a GET HTTP operation
, type: "get"
// tell jQuery we're getting JSON back
, dataType: "json"
// send the data to the CFC
, data: {
// the method in the CFC to run
method: "checkEmail1"
/*
send other arguments to CFC
*/
// send the email address entered by the user
, Email: $("#confirmEmail").val()
}
// this gets the data returned on success
, success: function (data){
// this uses the "jquery.field.min.js" library to easily populate your form with the data from the server
//$("#theErrorDivID").formHash(data);
if (data == 1) {
$("#theErrorDivID").html('<font color="red">This email exists, please activate your account.</font>');
}
if (data == 2) {
$("#theErrorDivID").html('<font color="red">This email exists, please login.</font>');
}
}
// this runs if an error
, error: function (xhr, textStatus, errorThrown){
// show error
alert(errorThrown);
}
});
</script>
</head>
<body>
<form name="myForm">
<p><label for="email">Email Address: </label><input type="text" id="email" class="text full-size" name="email" value="testgc11@globalcoal.com" /></p>
<p><label for="confirm-email">Confirm Email Address:</label><input type="text" id="confirmEmail" class="text full-size" name="confirmEmail" value="testgc11@globalcoal.com" /> <div id="theErrorDivID"></div></p>
</form>
</body>
</html>
CFC is below:
<cffunction name="checkEmail1" access="remote" returnformat="json" output="false" >
<cfargument name="Email" type="string" required="yes">
<cfstoredproc procedure="EmailExists" datasource="#request.DSN#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" dbvarname="@vr_Email" value="#ARGUMENTS.Email#">
<cfprocparam type="OUT" cfsqltype="cf_sql_integer" dbvarname="@Result" variable="Result">
</cfstoredproc>
<cfreturn Result />
</cffunction>
I'd recommend using a tool (like Firebug) which will allow you to view the URL request being sent to the server. This will be the easiest way to see what the problem is. You should be able to run the URL directly in your browser to view the error and find out what's going on.
I downloaded your files and tried running example.cfm on my CF8 dev box and received a JSON Parse error in an alert box. When I ran the cf7 version of the file it worked perfectly.
Shouldn't this be working in 8? It did in Railo.
Railo is different from ColdFusion 8. I don't have Railo installed to know what the differences are. The example only tested in Adobe ColdFusion 8.
Just perform the logic in the $("#findUser").click(function (){}); call in the change event on the select element.
Some things I have learned. I think I need code something like, e.g.,
$.each(msg.d, function(index, item) {
$("#ctrPrograms").get(0).options[$("#ctrPrograms").get(0).options.length] = new Option(item.progname, item.versionid);
});
for the "success: " setting in order to bind the returning data to my 2nd Select. However, I haven't cracked the syntax to do so, for either type of return format I use -- whether as a struct, e.g.,
{"1":{"seriesname":"Frontline","progname":"Law & Disorder","versionid":"213905"}}
or as a Query, e.g.,
{"COLUMNS":["SER_LGTITL","PG_LGTITL","VERSION_ID"],"DATA":[["Frontline","Law & Disorder",213905]]}
What I really need is to have the Select's option:value=VERSION_ID and option:text=PG_LGTITL. But even after reading the JQuery.AJAX docs and looking at lots of examples, I'm not seeing how to do this.
It looks like you've got the right idea, because this works for me:
http://jsfiddle.net/kLXZT/
You might also check out the TexoTela select plug-in:
http://www.texotela.co.uk/code/jquery/select/
It has a ajaxAddOption() method for loading options from an AJAX call.
Your JSFiddle code or the plug-in looks like exactly what I need to craft a solution. Many thanks.
I'm a seasoned CF developer working on a JQ project at the moment. All is going well, except I have this 'problem' submitting data through JQ to a CFC.
We are doing data driven sites, so our forms are extensive. As with your example, I can list all the form objects in the AJAX call. Say like this:
$.ajax({
url: "theComponent.cfc",
type: "post",
dataType: "json",
data: {
method: "setArticleNew",
posNr: completedForm.find("#posNr").val(),
artBez: completedForm.find("#artBez").val(),
artMarke: completedForm.find("#artMarke").val(),
..
},.....
Now this is all well and works fine. However, I'd much rather send only one data parameter with the whole form to the CFC and deal with it there. But that's where I have troubles.
I can do:
completedForm.serialize();
Which will create a string with the whole form which looks much like an URL with many URL parameters. But the problem is - what am I doing with it on the CFC-side?
I tried the CF serialzeJson()-Function, but it doesn't seem to be able to read it. Any idea?
a.
Just use my Field Plugin (http://www.pengoworks.com/workshop/jquery/field/fi...) to do the work for you.
You can either use the formHash() method on a form to grab all the fields and their values or use the fieldHash() to just grab fields based on a specific selector. Both methods return an object which is the name of the form field as the key and a value (which by default uses a comma separated list for multiple selections.) This basically will mimic the behavior as if the form had been posted to your server.
the error function is fired i get invalid json
but it return "Element Exception.RootCause type is undefined in arguments"
If you're using returnFormat="json", then you don't need the serializeJSON() function because ColdFusion is going to automatically serialize the results to JSON. So, what you're seeing is your user variable being double serialized to JSON.
If you remove the serializeJSON() function and just return the "user" variable, you should be ok.
$(function() {
$(".button").click(function() {
$.ajax({
url: "changedisablesysequipment.cfc?method=insertInfo"
,type: "post"
,dataType: "json"
,data: {
findRequestor: $("#findRequestor").val()
,Enablor: $("#Enablor").val()
,displayname: $("#displayname").val()
}
, success: function (data){
$("#frmMain").formHash(data);
}
, error: function (xhr, textStatus, errorThrown){
alert(errorThrown);
}
});
});
Here is my cfc
<cffunction name="insertInfo" access="remote">
<cfargument name="findRequestor" type="string" required="true" />
<cfargument name="Enablor" type="string" required="true" />
<cfargument name="displayname" type="string" required="true" />
<cfquery datasource="st_ops" name="putInfo">
INSERT INTO systemequipmenttype (disable)
VALUES ('#ARGUMENTS.Enablor#', '#ARGUMENTS.displayname#')
Where systemequipmenttype = '#ARGUMENTS.findRequestor#'
</cfquery>
<cfreturn Done />
</cffunction>
You've got to make sure your insertInfo method is returning JSON. Make sure it has the following attribute and value:
returnFormat="json"
Also, you'll want to make sure you're using some kind of developer tool that will allow you to monitor your HTTP requests (like Firefox, Chrome Dev Tools, an HTTP Proxy--like Charles--etc.) With any AJAX development, you'll want to be able to monitor the HTTP requests so you can see what's happening behind the scenes.
cfparam name="url.returnformat" default="json">
cfparam name="url.queryFormat" default="column">
On my html page I have a 2 select option.
My First selection option when I select I pull info from the database based on what I select.
when json is returned it populates a field call thename
How do I grab the value of "thename" via jquery on the return
EX thename = disable and make my other select statement selected.
select
option = disable selected
option = enable
/select
Nice plugin. I have been playing around with it a bit and see that seems to sort
alphabetically the best I can tell, and changes the index of the form elements as they are on the form. I am hacking around with the javascript some
with an aim to remove the sort as I want to keep the original the sequence of the fields as they are on the form. Do you have any quick edit suggestions to the js or am I missing something! Thanks
Henry
The JS should be creating the hash in the order the DOM object appear, however, ColdFusion generally returns keys in alphabetic order (there's no guarantee of key order in CF.)
The order of your keys really should be irrelevant.
