Another simple jQuery/ColdFusion example

This post is more than 2 years old.

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.

<html>

<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>

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:

<cfcomponent output="false">

&lt;cffunction name="getCategories" access="remote" returnType="query"&gt;
	&lt;cfset var q = queryNew("id,name")&gt;
	&lt;cfset var x = ""&gt;
	&lt;cfloop index="x" from="1" to="5"&gt;
		&lt;cfset queryAddRow(q)&gt;
		&lt;cfset querySetCell(q, "id", x)&gt;
		&lt;cfset querySetCell(q, "name", "Name #x#")&gt;
	&lt;/cfloop&gt;
	&lt;cfreturn q&gt;
&lt;/cffunction&gt;

</cfcomponent>

And then added the jQuery code to load and render these links:

//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/>" }

//"draw" s onto the screen
$("#nav").html(s)

})

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:

//listen for clicks on navLink $(".navLink").live("click", function(e) { var clickedId = $(this).attr("id") var id = clickedId.split("_")[1]

//load the detail
$.getJSON("data.cfc?method=getdetail&returnformat=json", {"id":id}, function(res,code) {
	var s = "&lt;h2&gt;" + res.NAME + "&lt;/h2&gt;"
	s += "This person is "+res.AGE + " years old."
	$("#content").html(s)
})
	
e.preventDefault()

})

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!

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Bill Tudor posted on 7/9/2010 at 7:52 PM

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...

Comment 2 by Raymond Camden posted on 7/9/2010 at 7:53 PM

How do you add a comment? Um... you just did. :)

Comment 3 by Bill Tudor posted on 7/9/2010 at 8:02 PM

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.

Comment 4 by Raymond Camden posted on 7/9/2010 at 8:18 PM

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.

Comment 5 by Dave posted on 7/9/2010 at 9:59 PM

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?

Comment 6 by Raymond Camden posted on 7/9/2010 at 10:48 PM

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.

Comment 7 by Brian posted on 7/9/2010 at 11:19 PM

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?

Comment 8 by Raymond Camden posted on 7/9/2010 at 11:20 PM

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.

Comment 9 by Brian posted on 7/9/2010 at 11:27 PM

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?)

Comment 10 by Brian posted on 7/9/2010 at 11:31 PM

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.

Comment 11 by johans posted on 7/10/2010 at 12:00 AM

Instead of live() you could just use jquery delegate() and capture the link events on the nav container
$('#nav').delegate('a.navlink','click',function(){ ... });

Comment 12 by Raymond Camden posted on 7/10/2010 at 12:07 AM

@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.

Comment 13 by Brian posted on 7/10/2010 at 12:22 AM

Searched a little and found this ... definitely seems to be version specific gotcha. Ouch.

www.aliaspooryorik.com/blog...

Comment 14 by trip posted on 3/29/2011 at 5:53 PM

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?

Comment 15 by Raymond Camden posted on 3/29/2011 at 5:56 PM

What does Firebug show you? Or Chrome dev tools?

Comment 16 by trip posted on 3/29/2011 at 6:02 PM

as soon as I hit post I knew that would be asked. The dom tab shows so much . . . what exactly am I looking for?

Comment 17 by Raymond Camden posted on 3/29/2011 at 6:03 PM

Look at the Net panel. Does it show the request being made?

Comment 18 by trip posted on 3/29/2011 at 6:12 PM
Comment 19 by trip posted on 3/29/2011 at 6:18 PM

please correct me if i'm wrong, but from that the call is being made

Comment 20 by Raymond Camden posted on 3/29/2011 at 6:18 PM

Ok, so we clearly see it making the request. Click on that line and look at the result. What do you see?

Comment 21 by trip posted on 3/29/2011 at 6:57 PM

{"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?

Comment 22 by Raymond Camden posted on 3/29/2011 at 7:02 PM

No, but go into your CF Admin. Is trusted cache turned on?

Comment 23 by trip posted on 3/29/2011 at 7:12 PM

Trusted cache - no
Cache Template In Request - yes
Component cache - yes
Save class files - yes

Comment 24 by Raymond Camden posted on 3/29/2011 at 7:20 PM

I think it was the component cache for you. In development, you don't want any of those 4 things turned on.

Comment 25 by trip posted on 3/29/2011 at 7:31 PM

duuuuuuuuuuude!! Thanks.

Comment 26 by Paul posted on 4/5/2011 at 8:11 PM

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.

Comment 27 by Raymond Camden posted on 4/5/2011 at 8:13 PM

Your friend for this is:

<cfsetting showdebugoutput="false">

You can put it in your pages that respond to Ajax requests.