cfajaxproxy - the other white meat
september 18, 2008 at 12:41pm
in ColdFusion
over the past few weeks, i've had the opportunity to start playing around with some of the AJAX functionality built into ColdFusion 8. cfajaxproxy makes me happy.
in a nutshell, cfajaxproxy creates a "bridge" between client side javascript and a ColdFusion CFC. it's nowhere near the full blown AJAX framwork that jQuery is, but rather reminds me of JSMX, which is a very lightweight AJAX framework that does little beside facilitating communication between client and server. while it allowed you to pass data from the client to the server, and receive data back, it assumed that you would write the corresponding code to manipulate the client based on the data that was sent back from the server. i'm pretty comfortable with javascript, so i was ok with that. cfajaxproxy is essentially the same thing. no bells-and-whistles per se, but as far as i'm concerned, that's perfectly ok.
i threw together a quick sample app showing how you can use cfajaxproxy to check off items in a to-do list (see it here). it's pretty straightforward in that it allows a task to be marked as complete, and that's it. i didn't add a way to mark an individual task as being incomplete after marking it complete, but that'd be the next logical step. if you play around with the demo, it might look like it's only manipulating an image on the client, but if you refresh the page after marking an item or two as complete, you'll see that it's actually updated the database on the server. so... what's going on in the code?
(i'm attaching the code as a zip file... feel free to download and follow along at home)
for the sake of keeping the demo relatively straightforward, there are only 2 files involved. tasklist.cfm (the client), and Tasklist.cfc (the server). the CFC has 3 methods:
- getAllTasks - returns all of the tasks in the Tasks table, including the task name and the value for 'isComplete' (boolean)
- markTaskComplete - this is the function that will be accessed from the client, and update a specific record with 'isComplete = 1'
- markAllTasksIncomplete - also accessed from the client... essentially a "reset" button.
UPDATE: probably worth mentioning that those methods that will be accessed via the cfajaxproxy "bridge" will have to have their access attribute set to "remote"!
most of what you're probably interested in is in the tasklist.cfm file.
i instantiate the CFC via createObject(), and then call the getAllTasks() method to initially populate the page with the tasks.
- <cfset tasks = createObject('component', 'Tasklist') />
- <cfset taskList = tasks.getAllTasks() />
nothing new there. the next line is where we bring cfajaxproxy into the mix:
- <cfajaxproxy cfc="TaskList" jsclassname="taskProxy">
the cfc attribute points to the CFC we want to be able to reference. for the purposes of the demo, it's in the same folder as the cfm. if the cfc were in a different directory (as woud likely be the case), the same pathing convention applies that you'd use in a createObject(). assuming the cfc resided in /com/foo/ (from the root), the value for the cfc attribute would be "com.foo.TaskList". the jsclassname attribute is simply the name you're going to use to refer to the "bridge" (bridging the gap between client and server) from your javascript. it's simply a variable name of your choosing.
now let's take a look at the javascript:
- <script type="text/javascript">
- taskProxy = new taskProxy();
- taskIDlist = "<cfoutput>#valueList(taskList.taskID)#</cfoutput>";
- taskIDArray = taskIDlist.split(',');
- markTaskComplete = function(taskID) {
- taskProxy.setCallbackHandler(markTaskComplete_Handler);
- taskProxy.markTaskComplete(taskID=taskID);
- }
- markTaskComplete_Handler = function(returnStruct) {
- if (returnStruct.SUCCESS) {
- document.getElementById('img' + returnStruct.TASKID).src = "checkmark.png";
- } else {
- msg = "Something went horribly, horribly wrong."
- msg += "\n\nThe Server Replied: " + returnStruct.MESSAGE;
- alert(msg);
- }
- }
- markAllTasksIncomplete = function() {
- taskProxy.setCallbackHandler(markAllTasksIncomplete_Handler);
- taskProxy.markAllTasksIncomplete();
- }
- markAllTasksIncomplete_Handler = function() {
- for (var i=0; i<taskIDArray.length; i++) {
- document.getElementById('img' + taskIDArray[i]).src = "checkmark_inactive.png";
- }
- }
- </script>
if you're not familiar with javascript, that might look a little intimidating, but it's fairly straightforward. on line 1, we're simply creating an instance of "the bridge" that is accessible to the client side javascript. the next 2 lines create an array of task IDs, which i'll use later on to uncheck all tasks. not important yet.
here's our first function. markTaskComplete. it's called via an onclick event on the grey checkmark image for an incompelte task. notice it receives a 'taskID' value as an argument. this is so it knows which task to update in the database.
the first thing i do in the function is set a callback handler. this simply specifies another function to call after the cfajaxproxy call is complete. what i do is put the client side manipulation in the callback handler. so after the record is updated, the callback handler function will change the checkmark image from grey (indicating incomplete) to green (indicating complete).
once the callback handler has been declared, i call the markTaskComplete method. this is a method in the CFC. this is where the magic happens. it's a client side javascript function communicating with a method in my CFC on the server. it passes the taskID argument to the cffunction on the server, and updates appropriate record.
it's probably worth showing the cffunction, since i threw a tiny bit of error handling in:
- <cffunction name="markTaskComplete" output="false" returntype="struct" access="remote" hint="i mark a task complete">
- <cfargument name="taskID" type="string" required="true" />
- <cfset var qMarkTaskComplete = "" />
- <cfset var returnStruct = structNew() />
- <cfset returnStruct.success = true />
- <cfset returnStruct.taskID = arguments.taskID />
- <cftry>
- <cfquery name="qMarkTaskComplete" datasource="tasks_demo">
- UPDATE
- Tasks
- SET
- isComplete = 1
- WHERE
- taskID = <cfqueryparam value="#arguments.taskID#" cfsqltype="cf_sq_char" />
- </cfquery>
- <cfcatch type="any">
- <cfset returnStruct.success = false />
- <cfset returnStruct.message = cfcatch.message />
- </cfcatch>
- </cftry>
- <cfreturn returnStruct />
- </cffunction>
notice that the function returns a structure (returnStruct). the structure will have at least 2 keys. 'success', which will bet set to true or false, depending on whether or not the update succeeded, and taskID. i pass back taskID so the callback handler knows which task to update on the client. optionally, a 3rd key will be added. if the query fails, i pass back a key of 'message' that contains the message value from the cfcatch. so, what happens with this structure that gets returned? glad you asked.
the callback handler method receives whatever is returned from the method. in this case, it's receiving the structure. so i'm able to run a conditional based on the value of returnStruct.SUCCESS (javascript is case-sensitive, and ColdFusion converts structure key names to all uppercase). if the update succeeded (if the value of returnStruct.SUCCESS is true), i use javascript to set the src attribute of the task's checkmark image to use the green (complete) image (checkmark.png). if the update failed for some reason (if the value of returnStruct.SUCCESS is false), i display a message to the user via javascript's alert() method.
that's it. the markAllTasksComplete() method is similar in what it does, so i won't go over that here, unless somebody requests it.
feel free to download the code from the link below, and take it for a spin. if you have questions, or need any clarification, drop me a comment. cfajaxproxy is fairly new to me, so it's worth stating that if there's anything that i might have been able to do differently, i'd like to hear that as well.
# Sep 18, 2008 @ 9:03 PM
This is basically all you need:
my_proxy.setForm('form_id');
my_proxy.save_method();
Another nice thing is if a ColdFusion error is thrown on the server, the cfajaxproxy code will parse through the message that comes back and rethrow it as a JavaScript error you can catch with a JavaScript try catch and handle appropriatley.
# Sep 19, 2008 @ 4:39 AM
# Sep 19, 2008 @ 10:21 AM
me personally, i've only used cfajaxproxy twice now (and this most recent time thought it was blogentry-worthy). most of my jQuery usage is using plugins ("hey, i need to mask this phone number form input... i wonder if there's a jQuery plugin... yes!").
so, i guess the answer would be no... i haven't found myself using the 2 side by side. at least, not yet. given a particular situation, i'm sure that i could... or that i'd find that (again, in a given situation), jQuery might negate the need for cfajaxproxy altogether. if you're referring to the fact that i "longhanded" my javascript (document.getElementByID() vs. the jQuery shorthand notation), yeah, when i'm just banging out something quick i revert back to my old school ways) :)
# Sep 19, 2008 @ 10:24 AM
setForm() looks very cool. i did notice it in the docs, but sort of glossed over it myself, since it wasn't really applicable to what i was doing at the time. i can definitely see it coming in handy tho. thanks for pointing it out.
the error handling doesn't impress me as much since i never have errors in my code :P
# Sep 19, 2008 @ 12:12 PM
# Oct 14, 2008 @ 8:36 AM
Thanks
Cliff
# Oct 15, 2008 @ 9:49 AM
Thanks for the post. I finally picked it appart for what most would consider an easy taks. The example was very easy to follow.
# Oct 15, 2008 @ 9:55 AM
can you try wrapping your returnStruct.supplierID and returnStruct.productID values in parseInt() functions? so, something like:
document.getElementById('img' + parseInt(returnStruct.supplierID) + parseInt(returnStruct.productID)).src = 'cross.jpg'; ?
let me know if that fixes it up for you.
# Oct 19, 2008 @ 9:41 PM
E