Easy AJAX using ColdFusion, jQuery and CFCs

Posted by Dan on Mar 3, 2011 @ 1:51 PM

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]

Categories: JavaScript, HTML/ColdFusion, jQuery, Source Code

32 Comments

  • With 1.5, you can now say $.ajax("example.cfc", {
  • It's marginally better to use returnFormat:'json' in the JavaScript instead of returnFormat="json" in the cfc. This makes the cfc a little more generic.
  • In 1.5, the success and error functions can be broken out. It's a slightly different syntax.
  • I would use type:'post' because I'm used to working with form='post' instead of form='get'
  • This example doesn't include a cfquery, which is the main point of the topic.
  • I would add ?queryFormat=row to the url so that I could address the fields by name, albeit in upper case.
    Ray had a blog post about it a while back.
  • @Phillip:

    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.
  • I've jut tried to do something similar with the following code and I keep getting the error:The email address needed for function checkEmail1 is required but not passed in.

    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>
  • @Nkonye:

    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.
  • Hi,

    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.
  • @Michael:

    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.
  • Ack! I just re-read one of my comments and I said queryformat=row when I meant to say queryformat=column!
  • Thanks much for your plugin. I've been able to use your code to successfully do a lookup via a CFC and return and display data from a single record in an input box. But I'm not seeing how to bind data from multiple records to a form select. Suggestions?
  • @Rick:

    Just perform the logic in the $("#findUser").click(function (){}); call in the change event on the select element.
  • @Dan, thanks for your reply. I think I should have phrased my question a bit differently. I can successfully pass a value from a first Select to a CFC and return a result. But I haven't yet figured out how to bind the results to a 2nd Select.

    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.
  • @Rick:

    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.
  • @Dan,

    Your JSFiddle code or the plug-in looks like exactly what I need to craft a solution. Many thanks.
  • Hi Dan, Thanks for this blog entry.
    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.
  • @Adi:

    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.
  • Thank you thank you thank you. I've been trying to get this to work all afternoon. Your post isolated a few of the small details I was doing wrong, but didn't find in other posts.
  • I've tried the above error but every time
    the error function is fired i get invalid json
  • I've tried to return the results like this <cfreturn serializeJSON(user) />
    but it return "Element Exception.RootCause type is undefined in arguments"
  • @Andy:

    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.
  • I am trying to post info back into a cfc using this. below is my JQ

    $(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>
  • @Bruce:

    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.
  • As a standard, I include the following at the top of every component that has remote functions:

    cfparam name="url.returnformat" default="json">

    cfparam name="url.queryFormat" default="column">
  • Got it working thanks for your help.

    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
  • Hi Dan,
    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
  • @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.
  • How to pass the return value inside the <cfscript>?
  • Newbie question. I'm using Awesomplete to autosuggest values in a text field which is working great. When a value is selected, it is populating as need other form fields after the call to my CFC. This works in Firefox and no other browser. Do you know why?

    Here's my code:
     <script type="text/javascript">
     <!--//
     // APU ID
     $(document).ready(function (){
      // when the "Find User" button is clicked, look up the data $("#findUser").click(function (){
      $("#apuid").change(function (){
       var apuid =
       $.ajax({
        // the location of the CFC to run
         url: "autosuggestname.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: "getData"
         /*
          send other arguments to CFC
         */
         // send the ID entered by the user
         , apuid: $("#apuid").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
         $("#default").formHash(data);
        }
        // this runs if an error
        , error: function (xhr, textStatus, errorThrown){
         // show error
         alert(errorThrown);
        }
       });
      });
     });
     //-->
     </script>

    Code for autosuggestname.cfc:
    <cfcomponent output="false">
      <cffunction name="getData" access="remote" returnType="struct" returnFormat="json" output="false">
        <cfargument name="apuid" required="true" />

        <cfset var searchByName = structNew() />
        <cfquery name="q" datasource="PSPROD">
          SELECT "Employee_Identifier","Last_Name" || ', ' || "First_Name" "FULL_NAME","First_Name", "Department", "Job_Title"
          FROM MYTABLE
          WHERE "Employee_Identifier" = <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#arguments.apuid#">
        </cfquery>
        <!---//
          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 (searchByName.name) then the keys will be returned in uppercase.
        //--->
        <cfset searchByName["legalname"] = q.FULL_NAME />
        <cfset searchByName["deptid"] = q.Department />
        <cfset searchByName["title"] = q.Job_Title />
        <cfreturn searchByName>
      </cffunction>
    </cfcomponent>

    Again, it works great in Firefox but not Chrome and latest IE.
  • Disregard, I found out how to make it work. I changed, ".click" to ".blur"

    From: $("#apuid").change(function (){
    To: $("#apuid").blur(function (){

Comments for this entry have been disabled.