Earlier today Rey Bango posted an excellent article about jQuery Templates (Not Using jQuery JavaScript Templates? You're Really Missing Out.) This was something I had meant to look at before but just never got around to it. If you haven't looked at this feature, please stop reading and catch up on Rey's post. After reading it, I thought it would be cool to employ the technique to update the demo I posted earlier today (Another simple jQuery/ColdFusion example). It took me a grand total of five minutes. Here is the original code used to render categories:
//"draw" s onto the screen
$("#nav").html(s)
})
$.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/>"
}
Compare that with the template version:
<script id="categoryTemplate" type="text/html">
<a href="" class="navLink" id="nav_${ID}">${NAME}</a><br/>
</script>
(more stuff here cut out...)
//Call the CFC to get queries
$.getJSON("data.cfc?method=getcategories&returnformat=json&queryformat=column", {}, function(res,code) {
var newData = []
for(var i=0; i<res.DATA.ID.length; i++) {
newData[newData.length] = { "ID":res.DATA.ID[i], "NAME":res.DATA.NAME[i]}
}
$("#categoryTemplate").tmpl(newData).appendTo("#nav")
})
As you see - I had to reform the data returned by ColdFusion to make it work with the template engine. I could do this at the CFC, but I like my CFC being abstract and not tied to any implementation. So I didn't trim many lines of code here (I may have actually went up by one or two), but the way it works is much cleaner now. I'm reminded of Adobe Spry, which to me has always shined in the area of actually displaying Ajax content.
Next up I rewrote the detail portion:
//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) {
$("#content").html($("#detailTemplate").tmpl(res))
}) e.preventDefault()
})
<script id="detailTemplate" type="text/html">
<h2>${NAME}</h2>
This person is ${AGE} years old.
</script>
(more stuff here....)
This modification was even simpler. My simple CFML struct worked just fine for the template engine. All in all a very painless modification, but I really dig it. You can find out more about the Template plugin here: http://github.com/nje/jquery-tmpl
Here is the entire page for the new version:
<html> <head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="jquery.tmpl.js"></script> <script id="categoryTemplate" type="text/html">
<a href="" class="navLink" id="nav_${ID}">${NAME}</a><br/>
</script> <script id="detailTemplate" type="text/html">
<h2>${NAME}</h2>
This person is ${AGE} years old.
</script> <script>
$(document).ready(function() { //Call the CFC to get queries
$.getJSON("data.cfc?method=getcategories&returnformat=json&queryformat=column", {}, function(res,code) {
var newData = []
for(var i=0; i<res.DATA.ID.length; i++) {
newData[newData.length] = { "ID":res.DATA.ID[i], "NAME":res.DATA.NAME[i]}
}
$("#categoryTemplate").tmpl(newData).appendTo("#nav")
}) //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) {
$("#content").html($("#detailTemplate").tmpl(res))
}) e.preventDefault()
})
})
</script>
</head> <body> <div id="nav"></div> <div id="content"></div> </body>
</html>
Archived Comments
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?
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.
Hy Ray,
i noted that you converted the result from the cfc in an array of hashes/structs, why not obtain a json array of hashes directly?
regards
Are you asking why I didn't return the data in a way that would work automatically? I certainly could have. However I try to keep my CFCs pure. Right not it returns a query. I may have other CFMs files that need this data and they would work with the query just fine. Just because one client (my code here) needs it in another way, it doesn't mean I should change the CFC.
That's my opinion anyway. Certainly if you are 100% sure the only "user" of this data is the Ajax front end, then yeah, go ahead.
Ray, I'm lost at the "Why are we doing this?" level. It seems like dropping data into templates is what coldfusion does best, and yet your pushing that off onto the client and jQuery. Coldfusion becomes a facade for the database. Maybe the simplicity of the demo page obscures the helpfulness of this approach. Am I making any sense that you can comment on?
There are a few reasons why I prefer this way. If you look at the first example, to render the content I had to build JS strings. While doable, it gets messy. There are a lot of plus signs, I have to escape the quotes or use single quotes. The template method is much easier to read and update.
As to your point about why I didn't use CF - or why I just use CF as a gateway to the db. I don't see anything wrong with that. CF is a great 'glue' between servers and front end clients. Just because it didn't do a lot here doesn't mean it wasn't useful. It also speaks to the power of the Ajax plumbing built within CF - specifically - I write my business logic with NO client in mind. But to expose it to an Ajax client I just make use of returnForm. And I'm done. To me that just shows CF shines.
Yes, you are absolutely right saying that the main goal is abstraction; however you can also structure your cfc to return data formatted as requested from front end: using a simple key in the url you can get data as query, object collection, pure json-ed query or json array of struct.
regards
Your talking about the structure of the data. You aren't talking about formatting in the display sense. That's what this template engine is for. Ie, take the data, wrap the name in h2, etc. I think it's a secondary discussion.
So if we ignore _html_ display, let's focus on your idea of making the CFC return different forms of data. IMO, this is a bad idea. The CFC is "right". It is asked to return a query of data and it does. Ajax can use it. CF can use it. It just works. In our example here we have a jQuery plugin which is a bit anal about the form of data. Therefore, I manipulate it before feeding it to the plugin. Could the CFC have done that. Sure! But why clutter it up when it isn't a CFC concern?
Now that being said, there have been cases where I've built other CFCs to do stuff like this. A good example is a service to handle calls for jqGrid. jqGrid also has specific needs for the form of the data. So sometimes I've built a CFC that both a) exposes another CFC remotely and b) handles changing the data. But I do NOT mess with the core CFC. It's business logic remains pure and is not concerned with any one client.
Great post Ray! Unfortunately I found this a single day late after I just did some extensive javascript outputting the now "long" way. Looks like I have some re-work to do! :)
Will try this definitely..quite good.