Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Mike Kingery and Jaana Gilbert

Creating A Remote AJAX Proxy In Javascript Without ColdFusion 8's CFAjaxProxy

By Ben Nadel on

I've been meaning to mess around with ColdFusion 8's new CFAJAXProxy tag. But, I decided that before I looked into the tag, I wanted to see if I could replicate the main functionality of the CFAJAXProxy tag with jQuery and some basic Javascript objects. I thought being able to wire it up myself would help me to understand the technology a bit better and get me more comfortable with the concept of making remote CFC calls. I figured I would start out with something very simple - a couple of related select boxes in which selecting one forces a remote call to populate the next.

 
 
 
 
 
 
 
 
 
 

First, let's get the ColdFusion components out of the way. Each of the CFCs represents an API object that has a single remote access method call.

NameService.cfc

  • <cfcomponent output="false">
  •  
  • <cffunction
  • name="GetNames"
  • access="remote"
  • returntype="array"
  • returnformat="json"
  • output="false"
  • hint="I return an array of names.">
  •  
  • <!--- Create an array of names. --->
  • <cfset var Names = [
  • "Anna",
  • "Jen",
  • "Kira",
  • "Rebecca",
  • "Nicole"
  • ] />
  •  
  • <cfreturn Names />
  • </cffunction>
  •  
  • </cfcomponent>

AdjectiveService.cfc

  • <cfcomponent output="false">
  •  
  • <cffunction
  • name="GetAdjectives"
  • access="remote"
  • returntype="array"
  • returnformat="json"
  • output="false"
  • hint="I return an array of adjectives.">
  •  
  • <!--- Create an array of adjectives. --->
  • <cfset var Adjectives = [
  • "Good",
  • "Awesome",
  • "Amazing",
  • "OK",
  • "Sub Par"
  • ] />
  •  
  • <cfreturn Adjectives />
  • </cffunction>
  •  
  • </cfcomponent>

PartService.cfc

  • <cfcomponent output="false">
  •  
  • <cffunction
  • name="GetParts"
  • access="remote"
  • returntype="array"
  • returnformat="json"
  • output="false"
  • hint="I return an array of body parts.">
  •  
  • <!--- Create an array of parts. --->
  • <cfset var Parts = [
  • "Legs",
  • "Calves",
  • "Face",
  • "Butt",
  • "Boobs"
  • ] />
  •  
  • <cfreturn Parts />
  • </cffunction>
  •  
  • </cfcomponent>

Nothing special going on there. I'm defining the RemoteFormat as JSON, but this doesn't much matter as I am explicitly setting this in my AJAX method calls below (which will override any RemoteFormat setting in the ColdFusion component method signature).

When I started to think about how I wanted to create the Javascript objects that wrapped around these remote method calls, I thought about what they all have in common. For starters, they are all AJAX calls; but, for each call, what do I really need to know? The name of the CFC that I am creating; the name of the method in the given CFC that I am invoking; a collection of arguments to that method; a function that handles the successful AJAX return; and, a method that handles the unsuccessful (error) AJAX return.

There's really nothing else that I need to know, at least not for my low-level example. So, I thought, I can create a base Javascript object that provides this generic AJAX functionality and response wiring. Each subsequent CFC wrapper can then extend this base proxy object and add its own method calls. Let's take a look at this base object:

  • // This is our base Remote CFC. It handles the core
  • // functionality for calling our CFCs from Javascript
  • // objects.
  • function RemoteCFC(){
  • // The name of the CFC being called.
  • this.Name = "";
  •  
  • // Return this object.
  • return( this );
  • }
  •  
  • // This handles the remote calls to the CFCs.
  • RemoteCFC.prototype.MakeRemoteCall = function(
  • strMethod,
  • objData,
  • fnSuccess,
  • fnError
  • ){
  • // Create a data struct and extend it with the method
  • // name and the data to be passed.
  • var objRemoteData = {};
  •  
  • // Extend the remote data set.
  • $.extend(
  • objRemoteData,
  • objData,
  • {
  • method: strMethod,
  • returnFormat: "json"
  • }
  • );
  •  
  • // Make the AJAX call to the remote method.
  • $.ajax(
  • {
  • type: "get",
  • url: (this.Name + ".cfc"),
  • data: objRemoteData,
  • dataType: "json",
  •  
  • // Bind success and failure methods.
  • success: fnSuccess,
  • error: fnError
  • }
  • );
  •  
  • // Return this for method chaining.
  • return( this );
  • }
  •  
  •  
  • // Create a new core remote object. We will need this
  • // to create the prototype chain such that the other
  • // proxy classes can extend this.
  • objRemoteCFC = new RemoteCFC();

As you can see, the RemoteCFC Javascript class has the property, Name, which is the name (path) to the CFC that is being called remotely. It's blank here, but each wrapper class that extends this base class will overwrite that property with the appropriate CFC path. It also has one method, MakeRemoteCall(), which performs the AJAX call to the given remote method in the given CFC. To make this method as generic as possible, most of the necessary data - including the remote method and the AJAX event handlers - are passed into it as arguments.

Notice that after I define the base RemoteCFC class, I am creating an instance of it. This is an important line of code because this remote instance will be used to define the prototype chain for each of our extending classes.

With the RemoteCFC base class fresh in our minds, let's take a look at one of the extending CFC wrappers, NameService:

  • // Create a Javascript proxy for this given CFC.
  • function NameService(){
  • this.Name = "NameService";
  • }
  •  
  • // Extend the core CFC Proxy functionality.
  • NameService.prototype = objRemoteCFC;
  •  
  • // Define remote method wrapper.
  • NameService.prototype.GetNames = function( objData, fnSuccess, fnError ){
  • this.MakeRemoteCall( "GetNames", objData, fnSuccess, fnError );
  • }

As you can see here, the constructor for the name service defines the Name property with the appropriate CFC path (I'm not actually overwriting the base value as it is one level up in the prototype chain). Then, and this is very important, we define the prototype of this object as the RemoteCFC instance. In doing so, we are essentially extending the RemoteCFC Javascript class. Now, the THIS scope of the wrapper class, NameService, will have access to the properties (Name) and methods (MakeRemoteCall) of the base class as if they were part of the wrapper class. Then, we define our wrapper method, GetNames(), which does nothing more than act as a facade to the MakeRemoteCall() base method.

Can you see where we are going with this? Each remote Javascript proxy becomes a very thin wrapper to the RemoteCFC base component, doing little more than defining the method call signatures. I am writing these classes and methods out explicitly, but it wouldn't take much more effort to grab the CFC meta data during page generation and programmatically write out these classes (creating a method wrapper for each remote-access method call).

Now that we've taken a look at the RemoteCFC base Javascript class and an example of the concrete wrapper class for an actual CFC, let's put it all together in one demo. As I said before, this demo consists of a group of related select boxes which are populated using one of the three remote CFC proxies:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Custom CFC Proxy Demo</title>
  •  
  • <script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.1.min.js"></script>
  • <script type="text/javascript">
  •  
  • // This is our base Remote CFC. It handles the core
  • // functionality for calling our CFCs from Javascript
  • // objects.
  • function RemoteCFC(){
  • // The name of the CFC being called.
  • this.Name = "";
  •  
  • // Return this object.
  • return( this );
  • }
  •  
  • // This handles the remote calls to the CFCs.
  • RemoteCFC.prototype.MakeRemoteCall = function(
  • strMethod,
  • objData,
  • fnSuccess,
  • fnError
  • ){
  • // Create a data struct and extend it with the method
  • // name and the data to be passed.
  • var objRemoteData = {};
  •  
  • // Extend the remote data set.
  • $.extend(
  • objRemoteData,
  • objData,
  • {
  • method: strMethod,
  • returnFormat: "json"
  • }
  • );
  •  
  • // Make the AJAX call to the remote method.
  • $.ajax(
  • {
  • type: "get",
  • url: (this.Name + ".cfc"),
  • data: objRemoteData,
  • dataType: "json",
  •  
  • // Bind success and failure methods.
  • success: fnSuccess,
  • error: fnError
  • }
  • );
  •  
  • // Return this for method chaining.
  • return( this );
  • }
  •  
  •  
  • // Create a new core remote object. We will need this
  • // to create the prototype chain such that the other
  • // proxy classes can extend this.
  • objRemoteCFC = new RemoteCFC();
  •  
  •  
  • // ------------------------------------------------- //
  •  
  •  
  • // Create a Javascript proxy for this given CFC.
  • function NameService(){
  • this.Name = "NameService";
  • }
  •  
  • // Extend the core CFC Proxy functionality.
  • NameService.prototype = objRemoteCFC;
  •  
  • // Define remote method wrapper.
  • NameService.prototype.GetNames = function( objData, fnSuccess, fnError ){
  • this.MakeRemoteCall( "GetNames", objData, fnSuccess, fnError );
  • }
  •  
  •  
  • // ------------------------------------------------- //
  •  
  •  
  • // Create a Javascript proxy for this given CFC.
  • function AdjectiveService(){
  • this.Name = "AdjectiveService";
  • }
  •  
  • // Extend the core CFC Proxy functionality.
  • AdjectiveService.prototype = objRemoteCFC;
  •  
  • // Define remote method wrapper.
  • AdjectiveService.prototype.GetAdjectives = function( objData, fnSuccess, fnError ){
  • this.MakeRemoteCall( "GetAdjectives", objData, fnSuccess, fnError );
  • }
  •  
  •  
  • // ------------------------------------------------- //
  •  
  •  
  • // Create a Javascript proxy for this given CFC.
  • function PartService(){
  • this.Name = "PartService";
  • }
  •  
  • // Extend the core CFC Proxy functionality.
  • PartService.prototype = objRemoteCFC;
  •  
  • // Define remote method wrapper.
  • PartService.prototype.GetParts = function( objData, fnSuccess, fnError ){
  • this.MakeRemoteCall( "GetParts", objData, fnSuccess, fnError );
  • }
  •  
  •  
  • // ------------------------------------------------- //
  • // ------------------------------------------------- //
  • // ------------------------------------------------- //
  •  
  •  
  • // Initialize the form when the document has loaded.
  • $(
  • function(){
  • // Create a new CFC proxy.
  • var objNameService = new NameService();
  • var objAdjectiveService = new AdjectiveService();
  • var objPartService = new PartService();
  •  
  •  
  • // Get the names.
  • objNameService.GetNames(
  • {},
  • function( objResponse ){
  • PopulateSelect( "name", objResponse );
  • }
  • );
  •  
  •  
  • // Hook up the onchange for the name.
  • $( "select[ name = 'name' ]" ).change(
  • function(){
  •  
  • // When the name changes, get the
  • // adjectives.
  • objAdjectiveService.GetAdjectives(
  • {},
  • function( objResponse ){
  • PopulateSelect( "adjective", objResponse );
  • }
  • );
  • }
  • );
  •  
  •  
  • // Hook up the onchange for the adjectives.
  • $( "select[ name = 'adjective' ]" ).change(
  • function(){
  •  
  • // When the adjective changes, get the
  • // body parts.
  • objPartService.GetParts(
  • {},
  • function( objResponse ){
  • PopulateSelect( "part", objResponse );
  • }
  • );
  • }
  • );
  •  
  • // Hook up the onchange for the parts.
  • $( "select[ name = 'part' ]" ).change(
  • function(){
  • DisplayMessage();
  • }
  • );
  • }
  • );
  •  
  •  
  •  
  • // This populates a given select box.
  • function PopulateSelect( strName, arrValues ){
  • var jSelect = $( "select[ name = '" + strName + "' ]" );
  •  
  • // Loop over the values and add as an option.
  • $.each(
  • arrValues,
  • function( intI, strValue ){
  • // Add option.
  • jSelect.append(
  • "<option value=\"" + strValue + "\">" +
  • strValue +
  • "</option>"
  • );
  • }
  • );
  • }
  •  
  •  
  • // I display the message based on the selections.
  • function DisplayMessage(){
  • jName = $( "select[ name = 'name' ]" );
  • jAdjective = $( "select[ name = 'adjective' ]" );
  • jPart = $( "select[ name = 'part' ]" );
  • jMessage = $( "#message" );
  •  
  • // Set message text.
  • jMessage.text(
  • "OMG! You think " +
  • jName.val() +
  • " has " +
  • jAdjective.val() +
  • " " +
  • jPart.val() +
  • ". Pervert!"
  • );
  • }
  •  
  • </script>
  •  
  • </head>
  • <body>
  •  
  • <h1>
  • Custom CFC Proxy Demo
  • </h1>
  •  
  • <form>
  •  
  • <p>
  • <select name="name">
  • <option value="">- -</option>
  • </select>
  •  
  • has
  •  
  • <select name="adjective">
  • <option value="">- -</option>
  • </select>
  •  
  • <select name="part">
  • <option value="">- -</option>
  • </select>
  • </p>
  •  
  • </form>
  •  
  •  
  • <p
  • id="message"
  • style="color: red ; font-weight: bold ; font-size: 20px ;">
  • <!-- Message will be injected here. -->
  • </p>
  •  
  • </body>
  • </html>

The whole thing works quite nicely. Like I said above, I haven't looked into the actual ColdFusion 8 CFAJAXProxy tag yet, so I'm not sure how powerful it is; but, all in all, I was quite surprised at how easy it was to create a Javascript remote proxy object. Now that I know this can be a very thin layer, I actually feel much more comfortable looking at the ColdFusion 8 version knowing that it's not an 800 pound gorilla of functionality, but rather a cute little bunny of a wrapper.




Reader Comments

I swear, Mr Nadel, you are looking over my shoulder. How else could you know exactly the things I'm working on and having trouble with?

Awesome example.

I've been wrestling with a strange issue with a jquery getJson request.
It basically fetches JSON to populate a select box. I've used exactly the same code before, without any troubles, but on this particular site, the JSON in being returned, but nothing is happening inside the 'success' function. I can't even generate a JS alert().

Does anyone have any ideas?

$("##countyID").change(function(){
$("##townID").html('');
$.getJSON('#application.rootURL#ajax/accountSelects.cfm', {countyID: $(this).val()},
function(j){
var options = '';
for (var i = 0; i < j.DATA.length; i++) {
options += '<option value="' + j.DATA[i][0] + '">' + j.DATA[i][1] + '</option>';
}
$("##townID").html(options);
$('##townID option:first').attr('selected', 'selected');
});
});

Many thank you's, kind people. :)

Reply to this Comment

@Matt,

Something is probably going wrong with the JSON. Maybe it has that security prefix, or what not. Try adding an ajaxError() method:

$( document.body ).ajaxError(
function(){
// Alert error.
alert( "Error!" );

// Log to FireFug.
console.log( arguments );
}
);

Reply to this Comment

Sorted.

It WAS attaching the security prefix (//) to the returned JSON.
Removed and all working.

Thanks Ben. I wouldn't have thought of that.

Rack up another beer for you whenever I finally manage to get to NY. :)

Reply to this Comment

I've missed something... you've got javascript calling a cfc directly? I thought only a call written in cfml or a web service could talk to a cfc?

Reply to this Comment

@Gary,

The Javascript is making an AJAX call to the component, not a direct call. That's why you have to pass in a success and failure event handler rather than get the data back from the method itself.

Reply to this Comment

Pretty slick. I'd like to see someone write a replacement for cfajaxproxy that uses jQuery under the hood. Ideally with the exact same API as cfajaxproxy so you could just replace a cfajaxproxy tag with jqueryproxy tag but leave all the other JS in place. (Hmm... maybe I'll do that.)

Reply to this Comment

Nice post Ben,
Just a thought you may want to have a look at the awesome jQuery extends method for simple inheritance in javascript.

Keep up the good work :p

Reply to this Comment

@shuns,

I am using the $.extend() method to build up the AJAX data request. However, I have never thought of using it to extend objects. I am not sure if I am crazy about the idea. Can you point me to an example of this concept?

Reply to this Comment

Ben, great experiment. It seems like you're writing/using a lot of code here (which is fine, but counter-jQuery-mentality).

Maybe I'm missing them, but it seems like there aren't many/any advantages to having to instantiate an object for each CFC you want to access, nor author JS functions for each CFC method you want to call. It seems like a more cfinvoke-style approach might be cleaner/more fun...

Reply to this Comment

So you'd have a JS class somewhere (or much better: extend jQuery with a single utility function) and then just call it at leverage time like so:

$.cfc( 'Component.Path', 'methodName', { /* args object */ } );

The args object could contain an optional callback (if no callback is provided, the $.ajax call is made SYNCHRONOUSLY by your plugin and the result of the call is returned to the point of the call using closure exploitation), and an optional argument collection (the CFC method arguments, if any).

Examples:

// Store result for re-use within the same block
var faveBand = $.cfc( 'Components.User', 'getFavoriteBand', { idUser: $( '#idUser' ).val( ) } );

// Call asynchronously with an anonymous inline callback
$.cfc( 'Components.Bands', 'getAllBands', { callback: function( bands ){ /* Do something with the bands recordSet here */ } } );

You'd just have a single proxy CFC sitting somewhere web-accessible that your plugin connects to that handles resolving the CFC calls and returning their results.

It seems like this would cut down on the memory usage on clients as well as totally reduce the code required to make a cfc call anywhere in your code. What do you think?

Reply to this Comment

@David,

Good point - you could just go with some abstract object. Althought, there is something nice about having a reference to the remote objects. Maybe something like:

var objService = $.cfc( "NameService" );

Then, you could invoke the method on that factory-created object:

objService.invoke( "methodName", { data }, fnSuccess );

This way, we can keep it all abstract which is what you are saying, but still give people the ability to have object references that they can pass around.

Reply to this Comment

Hi,
I want some suggestion like how to load dynamic contents inside a div id on ajax call.

I used to do it through javascript by passing the div ids and urls in the javascript function.

But is there any way I can achieve this through cf8 Ajax features.

Reply to this Comment

@Vicky,

I think you can use CFDIV to do this easily; however, you can also do this quite easily with jQuery's .load() method.

Reply to this Comment

I noticed I couldn't get my AJAX calls to work for jquery until I checked for them in my onRequestStart function inside my Application.cfc

I actually got this from somewhere else, just can't remember where off the top of my head:

<cfif listlast(arguments.TargetPage,".") is "cfc">
<cfset StructDelete(this, "onRequest") />
<cfset StructDelete(variables,"onRequest")/>
</cfif>

With this little piece of code inside my application.cfc it seems to resolve all my requests to my CFC's in jquery.

Hope this helps!!!

Reply to this Comment

@Noah,

Yeah, the OnRequest() method *can* cause some errors with CFC-based requests. What you have done is definitely one of the valid ways to handle it.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.