Ask Ben: Automatically Generating Remote CFC Proxies In Javascript

Posted February 17, 2009 at 3:31 PM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML, Ask Ben

Ben, I really like what you did with the javascript ajax proxy stuff this morning. In your post you mentioned that it wouldn't be hard to automate the generation of the javascript objects. I know this is a lot to ask, and you do so much already, but would you might demonstrating it? I wouldn't even know where to begin. thanks in advance!!

No problem at all. I'm a bit short on time today, so I am sorry if the explanation is a bit brief. Basically, what we want to do is take the static Javascript from my previous post and generate it based on the remote component meta data. ColdFusion component meta data contains all of the methods available in the target CFC; what we are going to end up doing is looping over that list of functions and outputting Javascript wrapper methods for each function that allows for remote access.

I've wrapped this functionality up into its own ColdFusion component, CFCProxy.cfc, but first, let's just look at an updated demo:

  • <!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">
  •  
  • <!--- Output the Javascript proxy classes. --->
  • <cfset objProxyService = CreateObject( "component", "CFCProxy" )
  • .Init()
  • .GenerateClass( "NameService" )
  • .GenerateClass( "AdjectiveService" )
  • .GenerateClass( "PartService" )
  • />
  •  
  •  
  • // 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>

As you can see here, the page-relevant Javascript is all the same; but, the generating of the remote AJAX proxy Javascript classes has been factored out into our new ColdFusion component:

  • <!--- Output the Javascript proxy classes. --->
  • <cfset objProxyService = CreateObject( "component", "CFCProxy" )
  • .Init()
  • .GenerateClass( "NameService" )
  • .GenerateClass( "AdjectiveService" )
  • .GenerateClass( "PartService" )
  • />

This CFCProxy.cfc ColdFusion component knows how to generate the base Javascript object (only once) and each unique proxy wrapper class.

Let's take a look at what's going on under the hood:

  • <cfcomponent
  • output="false"
  • hint="I hanlde creating Javascript AJAX proxies.">
  •  
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return an intialized object.">
  •  
  • <!--- Set up instance variables. --->
  • <cfset VARIABLES.Instance = {
  • GeneratedClasses = {}
  • } />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="CacheClass"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I store the class name to flag it as being cached (generated).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CFC"
  • type="string"
  • required="true"
  • hint="The path to the CFC for which we are creating a Javascript proxy."
  • />
  •  
  • <cfset VARIABLES.Instance.GeneratedClasses[ ARGUMENTS.CFC ] = true />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GenerateClass"
  • access="public"
  • returntype="any"
  • output="true"
  • hint="I generate the given class and return This reference for method chaingin.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CFC"
  • type="string"
  • required="true"
  • hint="The path to the CFC for which we are creating a Javascript proxy."
  • />
  •  
  • <!---
  • Check to see if this class has already been
  • genererated. If so, then we just want to return out
  • as we don't want to generate more than once.
  • --->
  • <cfif THIS.IsClassGenerated( ARGUMENTS.CFC )>
  •  
  • <!--- Terminate call. --->
  • <cfreturn THIS />
  •  
  • </cfif>
  •  
  • <!---
  • Check to see if we need to generate the base class.
  • This needs to be done before any other class is
  • created so that they can make use of the base instance.
  • --->
  • <cfif NOT THIS.IsClassGenerated( "__RemoteCFC" )>
  •  
  • #THIS.GenerateBaseClass()#
  •  
  • </cfif>
  •  
  • <!--- Generate requested class. --->
  • #THIS.GenerateExtendingClass( ARGUMENTS.CFC )#
  •  
  • <!--- Return This. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GenerateBaseClass"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I generate teh base Javascript proxy class.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Cache this class creation. --->
  • <cfset THIS.CacheClass( "__RemoteCFC" ) />
  •  
  • <!--- Define Javascript. --->
  • <cfsavecontent variable="LOCAL.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();
  •  
  • </cfsavecontent>
  •  
  • <!--- Return the generated Javascript. --->
  • <cfreturn LOCAL.Javascript />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GenerateExtendingClass"
  • access="public"
  • returntype="string"
  • output="true"
  • hint="I generate an exending Javascript proxy class.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CFC"
  • type="string"
  • required="true"
  • hint="The path to the CFC for which we are creating a Javascript proxy."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Cache this class creation. --->
  • <cfset THIS.CacheClass( ARGUMENTS.CFC ) />
  •  
  • <!---
  • Get the meta data for the given CFC. To do this, we
  • are going to create a temporary instance of the CFC
  • to more easily gather meta data.
  • --->
  • <cfset LOCAL.Functions = GetMetaData(
  • CreateObject( "component", ARGUMENTS.CFC )
  • ).Functions
  • />
  •  
  • <!--- Get the name of the class. --->
  • <cfset LOCAL.Name = ListLast( ARGUMENTS.CFC, "." ) />
  •  
  •  
  • <!--- Define Javascript. --->
  • <cfsavecontent variable="LOCAL.Javascript">
  •  
  • // ------------------------------------------------- //
  •  
  •  
  • // Create a Javascript proxy for this given CFC.
  • function #LOCAL.Name#(){
  • this.Name = "#LOCAL.Name#";
  • }
  •  
  • // Extend the core CFC Proxy functionality.
  • #LOCAL.Name#.prototype = __objRemoteCFC;
  •  
  • <!--- Loop over methods to create wrappers. --->
  • <cfloop
  • index="LOCAL.Function"
  • array="#LOCAL.Functions#">
  •  
  • <!--- Check to see if thsi is remote. --->
  • <cfif (LOCAL.Function.Access EQ "remote")>
  •  
  • // Define remote method wrapper.
  • #LOCAL.Name#.prototype.#LOCAL.Function.Name# = function( objData, fnSuccess, fnError ){
  • this.MakeRemoteCall( "#LOCAL.Function.Name#", objData, fnSuccess, fnError );
  • }
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfsavecontent>
  •  
  • <!--- Return the generated Javascript. --->
  • <cfreturn LOCAL.Javascript />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="IsClassGenerated"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I determine if the given class has been generated.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CFC"
  • type="string"
  • required="true"
  • hint="The path to the CFC for which we are creating a Javascript proxy."
  • />
  •  
  • <!--- Return its existence in the cache. --->
  • <cfreturn StructKeyExists(
  • VARIABLES.Instance.GeneratedClasses,
  • ARGUMENTS.CFC
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

Like I said, I don't have much time to go through this, but I think you can see how it works - you pass in a CFC class path and the CFCProxy.cfc checks to see if it has already generated this class before. It does this to make sure that there are no duplicate classes. If it has not yet generated a given class, it then grabs the meta data from a temporary instance of the target CFC. When gathering the meta data, we are creating an actual instance of the target CFC rather than calling GetMetaData() on the CFC class path only because it makes the returned data much easier to work with. Then, based on this meta data, I am outputting a Javascript class including all its remote methods.

I hope that helps!




Reader Comments

Feb 17, 2009 at 3:36 PM // reply »
11,246 Comments

The one thing I am a bit fuzzy on is how the URL pathing vs. the CFC pathing gets resolved. My demo page and my CFCs were all in the same path. I am not that familiar with root-contextual pathing, so that would probably need to be tweaked.


Feb 17, 2009 at 4:23 PM // reply »
28 Comments

I would recommend using getComponentMetaData() rather than getMetaData() since it doesn't create an instance of your component.


Feb 17, 2009 at 4:27 PM // reply »
11,246 Comments

@Tony,

Ahh, thanks. I always forget about that one.


Nov 29, 2009 at 6:58 PM // reply »
2 Comments

Ben thanks for another great tutorial here. I am experimenting with auto-generating lots of utility code like my CRUD cfcs, etc. on a fairly large application, and I'm so glad I found this post to help me generate those proxies faster. I was really excited about the AJAX stuff when CF8 came out, but since js frameworks by nature get updated more quickly than server frameworks, I have been working to roll my own cfajax framework. This just saved me a ton of time.

One little bug I noticed when testing the example code (using the CFCs from the previous example): each time a make a selection in a "parent" select field, the "child" has the value range appended again. Not a big deal as you quickly demonstrated proof of concept here which was the point!

Thanks again for your contributions to us little guys. :)


Jan 9, 2010 at 10:50 PM // reply »
11,246 Comments

@Kevin,

Glad to help. I am relatively new to the whole code generation concept; I experiment a lot with it, but haven't put it to too much use in production. Are you finding it really useful?


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools