<cfajaxproxy> – The Other White Meat

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&lt;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.

Don’t forget to check out the demo. Also, 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.