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!
Archived Comments
I give up: How do I 'blog' you re: the 'Another simple jQuery/Coldfusion example'. I get Twitter, I get eMail, I get the fact that I am posting you an eMail now. I have subscribed to your 'blog' of CF examples (twice now). But how do I simply post a quick 'blog' comment to you? Sorry, but I am feeling cfConfused again...
How do you add a comment? Um... you just did. :)
I think I get it now: Adding a Comment here IS blogging. My question was simply any tips on how to understand the getJSON() call and what the e.PreventDefault() was doing in the Jquery code. Apart from that I do more-or-less understnad your excellent example. Also, what makes the screen in my browser 'split' so that the links appear above the line and the 'detail text' response below it. Thanks.
getJSON basically means:
"I want you to fetch this URL and assume the result is JSON."
That means you don't have to eval the JSON to turn it into JS data. So basically, if the URL returns JSON, you end up with native JS data to work with. Hence me looping over it like a normal array.
Finally - e.preventDefault means: "I know I'm in an event handler. But abort the event. Don't do anything else." Normally if you click a link that has no URL, the page will reload. This stops that.
Does this mean that cfproxy is not needed and one can use straight jquery? I have been using a lot of jQuery lately with cfproxy. Am I adding unnecessary overhaed by using cfproxy?
If by "cfproxy" you mean cfajaxproxy, then yes, you do not need it. Well - I shouldn't say you don't need it. I'd say you probably do not need it if you are using jQuery instead. There may be cases where you can/want to use both.
Ignore comment on your previous post...meant to apply here...
Took me a bit (new to JQuery), but I was getting an error: "res.DATA.ID is undefined". Finally realized that I was dealing with case sensitivity -- your QueryNew used lcase id and name. Is this common for any data retrieved? Will it always be case sensitive?
Too late - but I'll cut and paste. ;)
JS is always case sensitive. Will it always be upper case? No. When you make a struct in CF using this notation: person.name = "RAY", then when it gets serialized to json, it becomes upper cased. If you do: person["naMe"] = "RAY", then the case of that key is preserved.
Thought I understood, but am now confused. You said
"When you make a struct in CF using this notation: person.name = "RAY", then when it gets serialized to json, it becomes upper cased"
Which is not what I observed... In the data.cfc, you did:
queryNew("id,name")
and yet, when the data was retrieved, I observed (in Firebug) that the case of the query keys was retained and not uCased...
Running CF 8 (make a difference?)
Just wanted to PS the response I saw:
{"ROWCOUNT":5,"COLUMNS":["ID","NAME"],"DATA":{"id":[1.0,2.0,3.0,4.0,5.0],"name":["Name 1","Name 2","Name 3","Name 4","Name 5"]}}
note the mixed case JSON return...weird.
Instead of live() you could just use jquery delegate() and capture the link events on the nav container
$('#nav').delegate('a.navlink','click',function(){ ... });
@Brian: Sorry - the CFML struct thing is the easiest way to explain the case n JSON. For QueryNew, you basically have no choice. In my Firebug (well, Chrome dev tools), its all upper case. Maybe a CF version diff.
@johans: Never used it - will need to read up on it.
Searched a little and found this ... definitely seems to be version specific gotcha. Ouch.
www.aliaspooryorik.com/blog...
ok, I'm totally missing something with the getJSON call. (Ithink) I've tried a few different examples and all of them end in the same way - blank white page. I've downloaded the zip of this example and am trying to run as is.
It just never appears to actually make the getJSON call, so no data, so no display. Is there something that I need to set in the CF admin? Is there a way to test of the call is actually being made?
What does Firebug show you? Or Chrome dev tools?
as soon as I hit post I knew that would be asked. The dom tab shows so much . . . what exactly am I looking for?
Look at the Net panel. Does it show the request being made?
ss at http://www.vcnadvertising.c...
please correct me if i'm wrong, but from that the call is being made
Ok, so we clearly see it making the request. Click on that line and look at the result. What do you see?
{"ROWCOUNT":5,"COLUMNS":["ID","NAME"],"DATA":{"ID":[1.0,2.0,3.0,4.0,5.0],"NAME":["Name 1","Name 2","Name 3","Name 4","Name 5"]}} . . . hmmm, that is correct. I just had an issue and had to restart my computer. Started everything back up and now it works. When creating a class with remote methods, does the cf server need to be restarted or at least "told" you are now serving remote? I remember when creating a webservice I had "refresh" the webservice in cf admin. Is it kinda the same thing?
No, but go into your CF Admin. Is trusted cache turned on?
Trusted cache - no
Cache Template In Request - yes
Component cache - yes
Save class files - yes
I think it was the component cache for you. In development, you don't want any of those 4 things turned on.
duuuuuuuuuuude!! Thanks.
Ray:
Thanks for the nice simple example. I found that turning on most of the custom debugging output in CF Admin will break the output and result in a blank screen as well.
Your friend for this is:
<cfsetting showdebugoutput="false">
You can put it in your pages that respond to Ajax requests.