Writing AJAX Return Value Logic On The Server
Traditionally, when dealing with AJAX, I have only ever had the server return JSON data or formatted HTML data. Then, in the client, I would take that return value and update the DOM using explicit manipulation. However, last night at the New York ColdFusion User Group, Rob Gonda demonstrated a very interesting idea. I am sure many out there have seen this, but this was new to me (and I am relatively new to large scale AJAX); Rob demonstrated a page where he didn't just return a data value from the AJAX call, but rather the return data wrapping in Javascript code that knew how to touch the client DOM and update the display.
I am not 100% if I understood what he was saying, but I think I got the general idea and I want to try to demonstrate it below. Here, I am creating a demo page that has two form buttons. When you click on either of them, the page performs an AJAX call using jQuery and then updates the innerHTML of a paragraph tag. The first button uses the simple-data method that I am more familiar with. The second button uses the Javascript-wrapped return value:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>AJAX Callback Experiment</title>
<script type="text/javascript" src="jquery-1.2.3.pack.js"></script>
<script type="text/javascript">
// AJAX call method 1.
function Method1(){
$.get(
"ajax.cfm",
{
method: 1
},
OnMethod1
);
}
// Call back handler for method 1. This one takes
// the return value and manually inserts the
// value into the output container.
function OnMethod1( strReturn ){
$( "#output" ).html( strReturn );
}
// AJAX call method 2.
function Method2(){
$.get(
"ajax.cfm",
{
method: 2
},
OnMethod2
);
}
// Call back handler for method 2. This one takes
// the return value and executes is. This assumes
// that the value returned is a script that includes
// hooks into page display.
function OnMethod2( strReturn ){
eval( strReturn );
}
</script>
</head>
<body>
<h1>
AJAX Callback Experiment
</h1>
<form>
<input
type="button"
value="Touch Me (Method 1)"
onclick="Method1();"
/>
<input
type="button"
value="Touch Me (Method 2)"
onclick="Method2();"
/>
<br />
<!---
This is where we are going to output our AJAX
return value.
--->
<p id="output"></p>
</form>
</body>
</html>
Take a look at the two call back handlers, OnMethod1() and OnMethod2(). The first one uses explicit jQuery code to update the DOM. The second one doesn't care what is returned, it just evaluates the value using the eval() method.
Ok, now let's look at the ajax.cfm page that is being called:
<!--- Param the method. --->
<cfparam name="URL.method" type="numeric" default="1" />
<!--- Check to see which method we are using. --->
<cfif (URL.method EQ 1)>
<!---
In method one, all we want to do is return the simple
value to the client.
--->
<cfset strReturn = "Yeah, just like that!" />
<cfelse>
<!---
In method two, we don't want to just return the value,
we also want to return the Javscript that tells the
client what to do with the return value.
--->
<cfsavecontent variable="strReturn">
$( "#output" ).html( "Oh baby, that felt good" );
</cfsavecontent>
</cfif>
<!--- Trim the return value. --->
<cfset strReturn = Trim( strReturn ) />
<!--- Return AJAX value length. --->
<cfheader
name="content-length"
value="#Len( strReturn )#"
/>
<!---
Stream result to the client (this will reset the content
buffer to make sure no additional data is returned).
--->
<cfcontent
type="text/plain"
variable="#ToBinary( ToBase64( strReturn ) )#"
/>
Here, you can see that when method 1 is requested, the return value is all that is sent back; however, when method 2 is requested, we are returning the jQuery code AND the inline value that is used to update the client DOM (this is the Javascript that is being blindly executed on the client).
Both of these methods work just as expected. I really like the second method. I am not sure if like it because it is new and unfamiliar, or if I like it because the logic is on the server which is where I am more used to playing. I have no recommendations at this point - I am just enjoying this new idea. I am letting it make the evaluation rounds in my mind. I am gonna see if I can get Rob Gonda to drop in here and make sure I really had any clue what he was talking about (I don't want to be misinforming anyone).
Want to use code from this post? Check out the license.
Reader Comments
I thought Javascript/AJAX calls to CFM pages had to use full URL ( $.get("http://servername/ajax.cfm" ) so that CF Server will actually parse/translate the file for you? Otherwise, aren't you just getting the raw text?
Does $.get("ajax.cfm", actually work?
@Todd,
Oh you better believe it works. It's just a relative URL to the current page (which was in the same directory for my test). But, I think when the rest executes, it actually changes the to a full URL for you. For example, none of my linked files (CSS / JS) have full URLs, only relative ones; however, if you look in FireBug, you will see that the request is all fully qualified URLs.
The browser takes care of all that goodness for you.
jQuery must be doing it then, we did this with regular ol' non-framework javascript weeks ago and we had to use the full URL. Another +1 for jQuery.
@Todd,
jQuery is just too awesome :)
Yeah, I drank the jQuery kool-aid already, I use it. Just don't use the full benefit of it.
jQuery is definitely awesome. I spend almost as much time doing cool UI things with jQuery as I do writing server-side code these days.
That's an interesting technique, having the server essentially issue commands to the client. I wonder if it could be used to completely bridge the events on both the client side and the server side together, letting event-driven frameworks essentially broadcast their events to the client, causing an event on the client side. Maybe you could even set off a chain of back-and-forth events between the client and server (though I can't think of a use case for this off the top of my head).
The biggest reason I am not a fan of method #2 (which is how I have done in the past) is due to the fact that now that the updating of information in the view is being generated in the business logic side of things, forcing high coupling.
Typically it is a good practice to reduce the knowledge that the business logic has of the view, and instead letting the server side of things maniplate data based on business rules, and sending this along back to your view. At this point the only coupling is what the view expects back from the server, as far as data goes.
Did I make any sense at all? lol
@Adam,
That was my concern as well. It may be that I only want the Touch Me return only ever on this one page, in which case it's fine for me to return the full DOM call and do a client-side eval(). If, however, I have another page or some other control elsewhere in my app that wants to play with the same Touch value generation but has a different DOM (maybe I'll use the value on a different page to change a fieldset legend or something), now I would need different return methods. If I return the simple string (or complex HTML, as Ben noted), then I can leave it to the view to go ahead and decide how the dispaly is relevant to it's context.
Something like that :D
A spoonful of jQuery helps the JS medicine go down. ;)
And that's cool. In the end it is simply a matter of what accomplishes the job, with a healthy balance of cost of development, future maintenance cost, and delivery time. Like I said, I've done the same before. :)
@Adam, what do you think about an ajax call building html on the server and then sending it back to the DOM? The classic example is generating options for a select menu, or rows for a table. I just find this to be cleaner than sending back a bunch of JSON and then building the DOM elements in the client, because you can just use CFOUTPUT and some html. Building up DOM elements from JSON is ugly, even with jQuery. I see what you're saying about coupling though.
It kind of depends on the application in question. I know that a lot of stuff I do I use ExtJS for Javascript interface design. All of that requires JSON data coming back to populate interface elements such as a combo box.
If you aren't using that kind of thing though, returning HTML is certainly a viable solution. The only point where that really breaks down is when you need to reuse that data in a different fashion... something different than the HTML for a combo box. This is why I typically prefer allowing my model to handle the retrieval of the data, and then pass that through components to transform this data in the desired format (i.e. JSON, XML). If I need to populate a combo box, I simply call the model components to get the data, and construct the combo box in the view. If I needed a JSON data set for some other reason, I can use the same model component, filtered through a transform component, to get the data in the desired format.
@Adam, jfish,
You make good points. Perhaps if the AJAX call went to some kind of proxy that then turned around and got the appropriate JSON data, you would't run into these use issues. For example, let's say for every page that has the use of AJAX, it has a server-side controller page like PAGE.ajax.cfm (just thinking out load here). Then, this page, just as the front controller, would take care of the request, pull in the right data, and then package it and send it back.
Of course, this might hurt the Don't Repeat Yourself concept. But then again, you are just moving variation from one file to another, so I don't think that's a concern.
@Adam,
The coupling between the interface and the controller does become much higher. But I guess that is true any time you return anything but JSON data; meaning, any time you return HTML data, even if it doesn't contain Javascript, it is much more coupled.
I am not saying that his is an excuse - again, I am new to this and just thinking out loud mostly.