Ask a Jedi: AjaxProxy and working with CFCs

Mark asks a question about working with CFCs over AjaxProxy:

I have a ColdFusion object (CFC) designed in an OO fashion with getters and setters. I want to use the new ColdFusion 8 ajax proxy stuff to create an instance of the object and use it in javascript like I do in ColdFusion. For instance, I want to instantiate it, set a parameter, call the load() method, then be able to use any of the getter methods that I've marked as remote. I've tried doing this, but from what I can tell, you can only use this js-cf bridge as one-time things (e.g. stateless?), with the object not "remembering" the other things you've done to it. Here is a example of what I want to do that I cannot figure out how:

Then in javascript: mycfc.setId(1);
mycfc.load();
alert(mycfc.getFirstName() + ' ' + mycfc.getLastName());

Anyway to do this? I can do it now calling one method at a time and passing in the id to that method, but that requires writing remote-specific methods that take the id for every call, look it up, and return that one piece of data (or stringing together multiple pieces) in the callback handler--but that is what I'm trying to avoid.

So this is a rather interesting situation. Most of the time I think people would use cfajaxproxy to talk to a service, not to a bean object. As Mark noticed, all the hits to the remote object are stateless. You could possibly store information in the session scope, but that would get a bit messy. So he was left with passing an ID every time.

You may ask - why did he create a proxy to the bean in the first place? Why not make a proxy to a service and return the bean CFC? Well CFCs are not something you can return via ajaxproxy. To be clear, you can return a CFC no problem. But on the server side, ColdFusion will return it as a struct and return the This scope. So if you CFC had this.name="Paris", you would end up with this in JSON: { "NAME":"Paris" }. Any methods your CFC may have had will not get returned.

I can think of a few things he could do. First off - instead of returning a bean, simply return a structure. I've written beans before where all the data was stored in variables.instance. This let me then have a getAll method that would simply return variables.instance.

He could then build into the service layer a getFoo(ID) type service where the CFC would instantiate the bean, but instead of returning it, would return bean.getAll. Back in JavaScript, you would treat the result not like a bean, but a simple structure.

Another alternative - and I'm not sure I'd recommend this - is to use the This scope. As I said, my beans normally write to variables.instance.X, where X is the property. If your bean instead wrote to the This scope, like this bean:

<cfcomponent>

<cffunction name="setname" returnType="void" access="public" output="false"> <cfargument name="name" type="string" required="true"> <cfset this.name = arguments.name> </cffunction>

<cffunction name="getname" returnType="string" access="public" output="false"> <cfreturn this.name> </cffunction>

</cfcomponent>

You can actually return this over the wire and automatically have a structure with the right data. You still wouldn't use getName() in JavaScript, but would instead just use the NAME property.

Archived Comments

Comment 1 by James Allen posted on 2/1/2008 at 6:02 PM

I'm currently doing something like this on the project I'm working on.

I basically do it like this:

Ajaxproxy CFC (in webroot) -> RemoteFacade.cfc (maintained by Coldspring) -> Service layer.

The frontend Ajaxproxy is a very simple CFC that takes params and then calls a method in the RemoteFacade.

The RemoteFacade takes these params and then makes calls to the service layer and returns anything that needs returning.

I have found this quite useful as the intermediate layer (RemoteFacade) can filter params so you aren't just sending the entire memento back to the client.

E.G I have a userLogin() method in Ajaxproxy and RemoteFacade. The Ajaxproxy method just passes over username and password, but then the RemoteFacade calls the login function in the UserService to retrieve a fully populated user bean which is automatically persisted in Session.

What I do now - in RemoteFacade - is to create a simple struct and only set the data I need. E.G:

<cfset returnData.isLoggedIn = user.isLoggedIn()>
<cfset returnData.error = user.getError()>
<cfset returnData.name = user.getName()>

I like this as it's filtering the data that is sent back as many of the fields in the user bean are not needed by the client.

Using a remote facade is nice as I can inject all of the dependencies using Coldspring which leaves the frontend Ajaxproxy.cfc nice and lightweight.

This should make refactoring much easier down the line..

Comment 2 by Mark Mazelin posted on 2/1/2008 at 8:33 PM

Ray:

Thanks for blogging about this! I think your first solution (and the ideas James submitted) would work, but I still am a bit frustrated by it.

After thinking through this a bit more, I thought of an analogy of what I want to do. I like how Flex and ColdFusion can pass CFC objects "back-and-forth" where the CFC object maps to an AS class. It would be oh-so-cool to have a CF tag (and/or wizard) that creates a JS class from a CFC that would enable you to do this with JS/CF. In fact it would be cool to be able to instantiate the JS object and chain the requests a la reactor and jquery: myJSObj = new JSObj then myJSObj.init().setid(1).load().getSomeAttr(). This would give you access to more than just the attributes of a bean, but also to other functions that bean may have.

But alas, since that functionality doesn't exist (should it?), I'll look into either returning a structure with the data I need or creating some type of intermediate ajax service layer.