I get a lot of questions about handling Ajax requests. What are some best practices? How do you format your requests? How do you taylor your functions in ColdFusion? Those sort of questions. We (the ColdFusion community) have embraced Ajax. Many of us spent so much time working mostly server side code on top of simple (and often poorly thought out and formatted) forms, but user habits have changed. And so have we. You have to move with the times to keep up in the digital rat race.

Let's start with the stuff most CFers already know; the server side stuff. When we make an Ajax request, most often we will hit a function within a CFC. We need data. You can hit a .cfm page, and return a bunch of preformatted HTML, but often that's a lot of data transfer when your really only need a few bits of data. HTML code can get heavy, what with all the tags and such. Similarly, you could return data in XML form (WDDX is native in ColdFusion). XML is sometimes a required format, for certain requests, but like HTML it can often be overly verbose. When working with server side requests from the client, you want to minimize data transfer as much as possible. That's why Flash performs best using the AMF format, and why most Ajax applications work with JSON.

When constructing your ColdFusion functions for remote requests, you don't have to write functions that can only be used via Ajax. In fact, that would probably be bad practice, as it would really minimize the code reusability of your functions. Basically, the only true requirement, at the server side, is that you set your function's access attribute to remote.

view plain print about
1/*    
2 *    COMPONENT SomeComponent
3 *    Just some example component
4 *
5 *    @name SomeComponent
6 *    @displayName SomeComponent
7 *    @output false
8 */

9component {
10    /*
11     *    FUNCTION SomeFunction
12     *    This is just some function you've written
13     *
14     *    @access remote
15     *    @returnType struct
16     *    @output false
17     */

18    function SomeFunction(required string arg1, required numeric arg2) {
19        var retVal = {"success" = true, "message" = "", "data" = ""};
20        // some function stuff here
21        return retVal
22    }
23}

That's it. Nothing to it. Did you see the required line? The one you need for making a remote request of this function?

view plain print about
1*    @access remote

Ok, we're off and running. Sort of. "Hey, Cutter, what's with returning a struct? I thought we were working with JSON?" Well, let me explain...

Have you ever worked on some Ajax call, loaded the page up in the browser, and nothing happened? You change variables, do this and that, and get nowhere? Finally you open up Firebug, watch the request, and see that you CF code has errored out for some reason. Instead of data coming back, the browser is getting the thousands of lines of HTML and Javascript that is the ColdFusion error display. In the immortal words of Charlie Brown: AAUUGGHH!

What happens if your user sees this behavior? Some variable isn't getting set when you think it is, or some db guy added or removed a column from your database without your knowledge. An error gets thrown, and you never even realize it. Ouch! Users are quick to abandon you. How can you gracefully handle these things?

Well, how do you deal with error handling within your application? If this answer is "I don't", then you might want to explore another career path. No, just kidding (kind of). If you don't use error handling, this is the perfect scenario to start. There is no reason why you're users should see (or, in this case, not see) these types of issues within your application. By carefully thinking things out, you can provide a better user experience.

view plain print about
1/*
2     *    FUNCTION SomeFunction
3     *    This is just some function you've written
4     *
5     *    @access remote
6     *    @returnType struct
7     *    @output false
8     */

9    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
10        var retVal = {"success" = true, "message" = "", "data" = ""};
11        var q = new Query();
12        try {
13            // do your query stuff, setting the result to the "data" key
14            if (!retVal["data"].recordCount) {
15                throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
16            }
17        } catch (any excpt) {
18            LogTheError(excpt); // Custom error logging for us
19            retVal["success"] = false;
20            if (!ARGUMENTS.debug) {
21                retVal["data"] = ""; // Clear the "data" key, so we don't pass unneeded bits back to the client. Override this by passing 'debug' into the request.
22                if (excpt.type eq "ourCustom") {
23                    retVal["message"] = excpt.message;
24                    retVal["errorCode"] = excpt.errorCode;
25                } else {
26                    retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
27                    retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
28                }
29            } else {
30                retVal["message"] = excpt.message;
31                // and anything else you might want
32            }
33        }
34        return retVal;
35    }

Now, there are probably better ways to write your catch statements, but I'm just trying to lay some foundation here. The basic idea is, taylor a message to send back to your users in the event of a failure. "ErrorCode?" Many enterprise products have custom error codes they use to denote that a specific error has occurred (like the above 'RC_000' for no returned records).

So, we have a success key, denoting whether the requested ColdFusion function did what we wanted. We have a data key that we use for returning the data generated by the request. And, we have a message key that we may, or may not, use only in the event of an error. We've also used and errorCode in this scenario, only in the event of an error. "Cutter, something doesn't look right?"

view plain print about
1// You use the quotes and this struct access notation to preserve the casing of key names, which is important in JavaScript
2    var retVal = {"success" = true, "message" = "", "data" = ""};
3    ...
4    retVal["message"] = excpt.message;

That should be the basics of the server side stuff. But, then the question comes, "What if our model isn't under the web root? What if the model CFC's aren't web accessible?" OK, fair enough. Then you'll need a proxy CFC. Let's regroup a sec, and show what this might look like.

view plain print about
1/*    
2 *    COMPONENT SomeComponent
3 *    Just some example component
4 *    This example sits outside the webroot, in a 'com' directory that's mapped in the CFIDE
5 *
6 *    @name SomeComponent
7 *    @displayName SomeComponent
8 *    @output false
9 */

10component {
11    /*
12    *    FUNCTION SomeFunction
13    *    This is just some function you've written
14    *
15    *    @access public
16    *    @returnType struct
17    *    @output false
18    */

19    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
20        var retVal = {"success" = true, "message" = "", "data" = ""};
21        var q = new Query();
22        try {
23            // do your query stuff, setting the result to the "data" key
24            
25            if (!retVal["data"].recordCount) {
26                throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
27            }
28        } catch (any excpt) {
29            LogTheError(excpt); // Custom error logging for us
30            retVal["success"] = false;
31            if (!ARGUMENTS.debug) {
32                retVal["data"] = ""' // Clear the "data" key, so we don't pass unneeded bits back to the client. Override this by passing 'debug' into the request.
33                if (excpt.type eq "ourCustom") {
34                    retVal["message"] = excpt.message;
35                    retVal["errorCode"] = excpt.errorCode;
36                } else {
37                    retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
38                    retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
39                }
40            } else {
41                retVal["message"] = excpt.message;
42                // and anything else you might want
43            }
44        }
45        return retVal;
46    }
47}

And the remoting proxy...

view plain print about
1/*    
2 *    COMPONENT SomeComponent_Remote
3 *    Just some example proxy component
4 *
5 *    @name SomeComponent_Remote
6 *    @displayName SomeComponent_Remote
7 *    @output false
8 *    @extends "com.cc.Examples.SomeComponent"
9 */

10component {
11    /*
12     *    FUNCTION SomeFunction
13     *    This is just some function you've written
14     *
15     *    @access remote
16     *    @returnType struct
17     *    @output false
18     */

19    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
20        return Super.SomeFunction(argumentCollection = ARGUMENTS);
21    }
22}

"Wait a second! I thought you said this post was about Ajax? Where's the JSON? Where's the Javascript?" Well, Ajax is useless without the data, right? Now that you have your CFC's and functions setup, let's look at the Javascript to make our requests. First we'll look at a basic JQuery Ajax call. First, let's set a global JS variable in the page.

view plain print about
1<cfscript>
2    param type = "boolean" name = "URL.debug" default = false;
3    
4    savecontent variable = "REQUEST.adder" {
5        WriteOutput('<script type="text/javascript">var debug = #URL.debug#;</script>');
6    }
7    REQUEST.addHeaderOutput &= REQUEST.adder;
8
</cfscript>
9...
10<cfhtmlhead text="#REQUEST.addHeaderOutput#" />

Next we'll setup our Ajax call.

view plain print about
1$.ajax({
2    url: '/my/cfc/location/SomeComponentRemote.cfc'
3    dataType: 'json',
4    data: {
5        method: 'SomeFunction', // The method we're calling
6        returnFormat: 'JSON', // Give us the return as JSON
7        debug: debug, // the global default 'debug' set earlier by ColdFusion
8        arg1: 'This is my argument',
9        arg2: 42
10    },
11    success: function (response, status, options) {
12        if (response.data) {
13            // Do something with the data
14        } else {
15            // This means the request failed, and response.data should contain
16            // a 'message' key, to present to the user, and an 'errorCode' key
17            // that you're application might act on
18        }
19    }
20});

Here's the same basic Ajax request, using Sencha's Ext JS.

view plain print about
1Ext.Ajax.request({
2    url: '/my/cfc/location/SomeComponentRemote.cfc',
3    params: {
4        method: 'SomeFunction', // The method we're calling
5        returnFormat: 'JSON', // Give us the return as JSON
6        debug: debug, // the global default 'debug' set earlier by ColdFusion
7        arg1: 'This is my argument',
8        arg2: 42
9    },
10    success: function (response, options) {
11        var response = Ext.util.JSON.decode(response.responseText)
12        if (response.data) {
13            // Do something with the data
14        } else {
15            // This means the request failed, and response.data should contain
16            // a 'message' key, to present to the user, and an 'errorCode' key
17            // that you're application might act on
18        }
19    }
20});

I'm always surprised at how few CFers know this little bit. In case you missed it, the key piece to your request parameters comes down to one important argument.

view plain print about
1returnType: 'JSON'

When this argument is passed in the Ajax request parameters, ColdFusion will automatically serialize your native ColdFusion response objects into JSON. This is what allows you to set your returnType to a struct in your function definitions.

Most of this stuff has been around for quite a while. Some use it a lot more than others. Some are just afraid of client side stuff in general. There's a ton of material out there on how to do these things. This is my way of working with it. That doesn't make it right, or better than anybody else's. It's just what I've developed as a working pattern over the years. How do you handle it?