Earlier this week I worked with a reader who was having issues using Ajax and ColdFusion. We exchanged quite a few emails and I thought I'd share the end result. This isn't anything new for the blog at all, so forgive the lack of originality, but I figured sharing yet another example couldn't hurt. I do want to spend a quick minute talking about his problem and how I went about solving it.
To begin - he was trying to create a simple system where his page had two dynamic regions. The first was meant to load a set of categories via Ajax. Once loaded, if you clicked on any of the categories it would use Ajax to load detailed information into the second region. Pretty simple, right? He had complicated the issue though by trying to mix both jQuery and cflayout. He was also trying to run these cflayout calls from the CFC being executed via an Ajax call. Bad call in my opinion. I mentioned that - in general - it is almost never a good idea to do layout within your CFC. Let your CFCs return their data and your front end client can render it. I'm not saying that's always going to be best, but I definitely recommended it for him.
The next thing I did was to start from scratch and add functionality piece by piece. I walked through the process with him to ensure he understood each step and what it was bringing to the "application" (if we can call 30 lines of code an application). So for example, my first build only had an HTML shell with jQuery loaded but not doing anything.
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
})
</script>
</head> <body> <div id="nav">NAV</div> <div id="content">CONTENT</div> </body>
</html>
<html>
In the code above you can see my two dynamic regions. The use of "NAV" and "CONTENT" was so that I could see the regions before the data loaded. Later on they would be removed.
So step one was to build a list of links. I created a simple CFC that would return hard coded data:
<cffunction name="getCategories" access="remote" returnType="query">
<cfset var q = queryNew("id,name")>
<cfset var x = "">
<cfloop index="x" from="1" to="5">
<cfset queryAddRow(q)>
<cfset querySetCell(q, "id", x)>
<cfset querySetCell(q, "name", "Name #x#")>
</cfloop>
<cfreturn q>
</cffunction> </cfcomponent>
<cfcomponent output="false">
And then added the jQuery code to load and render these links:
//"draw" s onto the screen
$("#nav").html(s)
})
//Call the CFC to get links
$.getJSON("data.cfc?method=getcategories&returnformat=json&queryformat=column", {}, function(res,code) {
//create a string for our result
var s = ""
for(var i=0; i<res.DATA.ID.length; i++) {
s += "<a href='' class='navLink' id='nav_" + res.DATA.ID[i]+ "'>"+res.DATA.NAME[i]+"</a><br/>"
}
The next step was to enable clicks on the links. Now jQuery makes it easy to add event handlers - but did you notice how the links weren't loaded until the Ajax call? In order to support listening to an event for items that do not exist when the page is created, you need to make use of the jQuery live function:
//load the detail
$.getJSON("data.cfc?method=getdetail&returnformat=json", {"id":id}, function(res,code) {
var s = "<h2>" + res.NAME + "</h2>"
s += "This person is "+res.AGE + " years old."
$("#content").html(s)
}) e.preventDefault()
})
//listen for clicks on navLink
$(".navLink").live("click", function(e) {
var clickedId = $(this).attr("id")
var id = clickedId.split("_")[1]
For the most part, this looks just like a click handler - switching to the "live" format though makes it work with items loaded in via Ajax. The only real weird part here is how I get the ID. I created my link IDs so that they contained the primary key in the string. This allows me to get the entire string and just split it. Once I've done that, it's trivial to call my CFC and get the detail. Here's that CFC method:
<cffunction name="getDetail" access="remote" returnType="struct">
<cfargument name="id" type="numeric" required="true">
<!--- fake data --->
<cfset var result = structNew()>
<cfset result.name = "Number #arguments.id#">
<cfset result.id = arguments.id>
<cfset result.age = arguments.id * 2>
<cfreturn result>
</cffunction>
And that's it. Just in case folks want to play with the code I've zipped it up and attached it to the entry. As I said in the beginning, I've done a bunch of this stuff before, but if there is anything here that is confusing or needs more explanation, please ask!