My AJAX / ColdFusion Response Framework (er, um ...Methodology)
I hesitate to call this a framework (as that seems to bring the pain from the Community). This is not really a framework, but rather a methodology that I am finding quite nice when developing a ColdFusion response to an AJAX request. I am sure stuff like this is all over the place, but in typical blood-and-guts fashion (any Dorian Yates fans out there???), I like to reinvent the wheel to figure out what works best for me (laugh at me all you want, but I am learning stuff).
For every AJAX request that comes to the application, I create an request struct (to become a Javascript object) that contains three keys:
Success
This is a boolean that defines wether or not the AJAX request was executed successfully. This will be true (be default) if everything goes according to plan. This will be false if the page has an error, required data wasn't passed, or anything else that causes things to not run smoothly.
Message
This is a string that contains an optional message describing the events that have taken place.
Data
This is variable that could be a struct, string, boolean, or anything else that needs to be returned. This structure will depend on the request that was made and any errors that occur during the request (ex. after a ColdFusion error, this value contains the detailed error info - CFCATCH.Detail).
Here is the basic skeleton of the page that provides the AJAX request API and ColdFusion response:
<!--- Kill extra output. --->
<cfsilent>
<!--- Create the default response object. --->
<cfset REQUEST.Response = StructNew() />
<!--- This is the flag for API success. --->
<cfset REQUEST.Response.Success = true />
<!---
This is a message that may or may not be populated
by the request API.
--->
<cfset REQUEST.Response.Message = "" />
<!---
This is the data that is being returned for
the API call.
--->
<cfset REQUEST.Response.Data = "" />
<!---
This is the ColdFusion component that will be used
to store any API data validation errors. It is really
just an array around which I have wrapped some
extra functionality
--->
<cfset REQUEST.FormErrors = APPLICATION.ServiceFactory.GetFormErrorCollection() />
<!--- Try to perform the API call. --->
<cftry>
<!---
Param the action attribute. This is the value
that will determine which method of the API is
being called.
--->
<cfparam
name="REQUEST.Attributes.action"
type="string"
/>
<!---
Check to see which API action we are performming
for this AJAX request. ALL requests will be
handled here, either explicitly or by default case.
--->
<cfswitch expression="#REQUEST.Attributes.action#">
<!--- .... API CODE GOES HERE .... --->
</cfswitch>
<!---
Catch any errors that have occurred during the AJAX
request processing.
--->
<cfcatch>
<!---
Set the success flag to false. Since this is
error we are going to assume that the overall
process was not successful.
--->
<cfset REQUEST.Response.Success = false />
<!---
Set the message from the error. Not every error
produced by ColdFusion has a really nice message
so let's check the message length. If there
is no message, try to use the detail error info
as the message.
--->
<cfif Len( CFCATCH.Message )>
<!--- Use the standard message. --->
<cfset REQUEST.Response.Message = CFCATCH.Message />
<cfelseif Len( CFCATCH.Detail )>
<!--- Use the detail as the message. --->
<cfset REQUEST.Response.Message = CFCATCH.Detail />
<cfelse>
<!---
Something went wrong. ColdFusion did not
provide a message or a detail about the
error that occurred.
--->
<cfset REQUEST.Response.Message = "Unknown error occurred." />
</cfif>
<!--- Set the data from the error detail. --->
<cfset REQUEST.Response.Data = CFCATCH.Detail />
</cfcatch>
</cftry>
<!---
ASSERT: At this point we have processed the API. We may
have thrown some sort of error in the process, or the API
event may have gone perfectly. Either way, we have a fully
populated REQUEST.Response object at this point that we
can return.
--->
<!---
Let's check to see if we have any data validation errors.
We are going to handle this centrally so that each API
action doesn't have to handle it individually.
--->
<cfif REQUEST.FormErrors.Size()>
<!---
Set the success flag to false. If there are any
errors in our form error collection then we will
assume that the overall action was not successful.
--->
<cfset REQUEST.Response.Success = false />
<!--- Set data validation message. --->
<cfset REQUEST.Response.Message = "Please check your data." />
<!--- <br>
Set the validation errors as data. For this, we are
going to return the array of error messages.
--->
<cfset REQUEST.Response.Data = REQUEST.FormErrors.ToArray() />
</cfif>
<!---
Return the response object. The response should be
treated as an inline request.
--->
<cfheader
name="content-disposition"
value="inline"
/>
<!---
Set the content to be plain text. Then, convert the
AJAX response object to a JSON (Javascript Object
Notation) string. In order to return this using
the VARIABLE attribute, we must convert this string
to a binary data object.
--->
<cfcontent
type="text/plain"
variable="#ToBinary( ToBase64( REQUEST.UDFLib.AJAX.CFToJSON( REQUEST.Response ) ) )#"
/>
</cfsilent>
So far, I am really liking this implementation. I like that the response has a very standard, yet at the same time, a highly flexible data return. I like how easy it is to tell if the request was successful. I also LOVE using the VARIABLE attribute of the CFContent tag to return the response object. Doing it this way allows me to never come out of my CFSilent tag and releases me of the requirement to manually write my data to the response buffer. Very slick! (Try this... I promise you will never want to do it any other way).
I am still figuring out how I want to handle the CFSwitch statement. Right now, I am just including all the action inline to the document (as I am not working on very large scenarios here), but down the road, this is probably something that I will want to move into CFIncludes for each action.
As you can see from the code, I am converting my responses to JSON, which I think, it the best way to handle AJAX. It just gives you so much power and freedom to be flexible, yet at the same time, allows you to work in a highly structured environment. XML is really cool, but for like 99% of my use cases, it really adds no benefit that I can see.
Well anyway, I am a total AJAX NOOBIE!!! So, please take the above with a grain of salt. I have been doing AJAX for about 2 weeks and have only implemented this in my Code Chat Beta 1.0 and another private project. There are many well established AJAX frameworks out there already which are probably better than this. But I know what I like, and I like this :)
Want to use code from this post? Check out the license.
Reader Comments
Just a slight suggestion, in your code comments maybe you could implement a way to collapse the comments in your code. A lot of times I find it difficult to just read the code because of all the comments. A collapsible thing for comments would be pretty cool I think.
That could be cool. I am still looking to improve the code display overall. I will definitely take this into account. Thanks.
Don't be too worried about being new to Ajax. Ajax is awesome, but there isn't much to it. It's more about being smart about where you use it. I bet you already have a pretty good hang of it. As you progress, I'm sure you'll build yourself a cool javascript ajax framework to go along with your CF onea and it'll automate all sorts of things, like automate form submissions with built-in client-side validation, for example. With jQuery, Prototype, YUI, or whatever you want to use, it'll be a breeze, you'll love every minute of it. This stuff is so much fun it should be illegal.
I agree, JSON and AJAX go together very well. I too use the Structure to JSON and send a message as well as data, it feels very natural since the dot notation follows from CF to JS.
I do not use the header and content tags. I just have the output tag touching the closing silent tag.
@Kris,
Yeah, I used to write the output after the close CFSilent tag... but in CFMX 7 when the variable attribute came out, there's something about it which just seems so elegant. No real difference.
Hehe, Dorian Yates. He was kind of bloated, come to think of it...;)
Are you trying to imply that I am bloated?!?!? Ha ha :)
Ben,
I uses a similar approach which is more CFC based although it doesn't handle errors at this point. I have an AJAX Facade cfc which has an handle to the internal ColdSpring Bean Factory. All methods in this CFC have a signature of access="remote" so the clients can call those methods directly. These method in turn calls the inner API method by using the ColdSpring factory.
Thanks
I agree with the use of cfcontent and cfsilent.
I was hoping that with Scorpio CF would build in a mechanism. If you've tried to write XML and only XML to the response you've had to deal with this.
I would like to see something like
<cfprocessingdirective ignoreoutputbuffer="yes" >
Then see
<cfresponse data="Hello world" >
<cfresponse xmlobject="#anxmlobject#" >
This is close to the cfcontent tag, but could be called over and over instead of building a very large CF variable.
Btw you can use this same approach to stream JPG or CSS files and use it to track when someone reads an email or that sort of thing. Some readers will strip off the the query string...so if you had
<img src="http://blahblah.com/smallimage.cfm?yourid=15" >
smallimage.cfm will get called, but yourid won't be passed.
The other way around this is to have custom 404 handlers. In IIS you can tell a 404 message to be direct to a .cfm. In the script you get the path. You can build a path that looks legit, but ulitmately is just your id encapsulated...like
http://blahblah.com/myfakedirectorywhere404sarebeingdirected/youridis-15/thisisntarealjpg.jpg
Your custom 404 handler will then stream a transpatent image. The end user will not know it is there. Most email programs will not download jpg's...but they usually will download CSS files. So you can track when someone reads your confirmations or other generated emails to see how they picked up.