CF8: Of Star Wars, Layer Cake and CFAJAXPROXY

Now that I’ve covered the server-side JSON support we have in place, it’s time to talk about something a little different - the client-side tools that CF8 provides to leverage the stuff we’ve put in on the server.CF Developer Encased in Carbonite

If you’re a CF developer, you’d have to be encased in carbonite not to have heard that CF8 adds a slew of new tags and functions to help you build AJAX applications. The first of these to be built was the CFAJAXPROXY tag.

Once automatic JSON<->CFML serialization and deserialization was built into CF’s remote CFC invocation layer, the next logical step was to provide a mechanism that would allow invocation of CFC functions remotely from a web browser in the simplest manner possible. The easy way out would have been to just write a JavaScript function that works like CFINVOKE, perhaps something like this:

ColdFusion.invoke(<Path to CFC>, <Function>, <Arguments>)

We did consider it, but it just didn’t feel natural enough…

Introducing CFAJAXPROXY

For those of you who have been performing wall ornamentation duties in Jabba’s palace, CFAJAXPROXY does pretty much what it says it does - it creates a JavaScript AJAX proxy to a CFC. Let’s say you have the CFC I wrote for interfacing with del.icio.us web services, saved as “delicious.cfc”. To get access to it from a web browser’s JavaScript environment, you could simply do this in your CFM:

<cfajaxproxy cfc="delicious">

This creates a JavaScript “class” named “delicious”, with functions matching the remote functions on delicious.cfc. CFCs referenced can be in relative or absolute paths - you can use dot-delimited CFC names, just as for createObject(). The generated “class” is named with the CFC name by default, but could be set to have a custom name of your own using the “jsClassName” attribute.

Call the functions remotely from your browser, like this:

<script>
function getDeliciousTags(user)
{
    var d = new delicious();
    var tags = d.getTags(user);
    for (tag in tags) alert("Tag " + tag + " has " + tags[tag] + ” posts”);
}
</script>

Ok, so I’m tooting my own horn here, but that’s gotta be cooler than a snowstorm on Hoth! Under the covers, the call to getTags is formed into a CFC HTTP invocation, with a JSON-encoded argumentCollection and returnFormat set to JSON. By default, the call is synchronous, and performed using a HTTP GET. To switch to an asynchronous call, you can set a callback handler function to process the response:

<script>
function getDeliciousTags(user)
{
    var d = new delicious();
    var tagsHandler = function(tags)
    {
        for (tag in tags) alert("Tag " + tag + " has " + tags[tag] + ” posts”);
    };
    d.setCallbackHandler(tagsHandler);
    d.getTags(user);
}
</script>

The request is submitted asynchronously, and the response is provided as a JavaScript variable to the callback function. In this example, the response is a JavaScript object, with all the del.icio.us user’s tags as keys, and the number of posts per tag as values.

CFAJAXPROXY vs. The Sarlacc

The generated proxies provide lots of built-in functions to let you tweak their behaviour, more than a Sarlacc has tentacles!

Sarlacc tentacles vs. CFAJAXPROXY functions

  • setCallbackHandler(<JavaScript function>) - As illustrated above. Automatically switches the proxy to asynchronous operation.
  • setErrorHandler(<JavaScript function>) - Specify a function to handle any server errors. The function is provided with the server error code, and an error message. Automatically switches the proxy to asynchronous operation. Here’s a tip: if you switch “Enable Robust Exception Information” on in the administrator, you’ll get detailed error messages back here, rather than the vanilla “Internal Server Error”.
  • setAsyncMode() - Sets the proxy to operate asynchronously, regardless of whether or not callback and error handlers have been set.
  • setSyncMode() - Sets the proxy to operate synchronously.
  • setHTTPMethod(<get|post>) - Sets the HTTP method to use for the invocation.
  • setQueryFormat(<row|column>) - Sets the format in which queries should be returned. See Ben Nadel’s blog entry for more details of query serialization options.
  • setReturnFormat(<plain|json|wddx>) - This one’s not in the public beta, but is planned for the final release. Sets the return format for the call, JSON by default. Something cool to do with it - get XML documents back by setting return format to “plain” and setting the response content type to “text/xml”.
  • setForm(<form id>) - Probably my favourite function. This one tells the proxy to pass the values from the specified form as arguments to the next remote function call, named by their form field names.

In sum, CFAJAXPROXY provides an almost ridiculously simple way to invoke CFCs from your AJAX applications, along with a gaggle of built-in functions that put you in control of a proxy’s behaviour. Whoever said AJAX was hard?

There is an alternate form of CFAJAXPROXY, used for binds - I’ll cover that later, when I look at bind expressions in detail.

CF AJAX Layer Cake

CFAJAXPROXY and JSON support as Layer Cake

The server-side JSON functions, JSON web services, and CFAJAXPROXY together form an architecture that fits together quite neatly, each layer building on the one beneath it. I’m particularly fond of APIs exposed in this manner. We could just have built CFAJAXPROXY, and hidden all the stuff that makes it run, which would have been a lot easier on us; one magic tag, less individual pieces to test, document and maintain.

Instead, exposing each layer of API lets you build applications as you will - just use the JSON functions, and build your own web service layer, or use the web service support with your favourite JavaScript toolkit consuming it, or use the whole stack, with CFAJAXPROXY managing server invocations and CFML<->JSON<->JS (de)serialization. Choice is good.

Let us close in prayer: Forgive me this day my cheesy Star Wars references and gratuitously edited layer cake image, they’re all smoke, mirrors, and Jedi mind tricks… But then, we all know who to blame for spreading the Star Wars meme in the ColdFusion community, now don’t we? ;)