Open-Source Review - AjaxCFC
Posted on Feb 25, 2006
Today I updated the ColdFusion Open-Source List to use AJAX on the filter by category. While this isn't the most spectacular use of AJAX, it was surprisingly easy to implement using the built-in functions of Rob Gonda's AjaxCFC. Put it this way, I spent all of an hour-and-a-half learning how to use AjaxCFC and implementing this feature, and a good chunk of that time was spent reminding myself of some DOM basics. This being my first foray into anything AJAX, I'd say that ain't bad.Getting Started
The obvious first step is to download the AjaxCFC zip file. When you unzip it you will see the core files folder which contains ajax.cfc and the necessary javascript files, a documentation folder which includes the documentation pdf and an examples directory that contains about 9 different examples covering various uses for the tool. To be honest, I didn't even go through the examples, but what I was trying to accomplish ended up being straightforward enough that I didn't need to (though, perhaps, it would have saved me some time nonetheless).
The documentation is all of 6 pages, which, to be honest, seemed a little brief to me considering all that AjaxCFC can do, but in the end it did provide all the information I needed to get started (which is a tribute to how simple AjaxCFC is to use).
The Code
At the risk of purely repeating the documentation, the first thing you need to do is add the following includes to your document:
<script type='text/javascript'> _ajaxConfig = {'_cfscriptLocation':'com/ajax_cfoslist.cfc', '_jsscriptFolder':'js'}; </script> <script type='text/javascript' src='js/ajax.js'></script>
The first script is my ColdFusion component location. You can alternately supply this with each call, but I am using only one, so it simplifies the later calls to specify this here. The second is the location of the provided javascripts directory. The second script is the primary JavaScript include.
Next let's take a look at my component:
<cfcomponent extends="ajax"> <cfset variables.reactor = CreateObject("Component","reactor.reactorFactory").init(expandPath("../reactor.xml")) /> <cffunction name="getResources" access="private" output="false" returntype="string"> <cfargument name="args" required="true" type="array" /> <cfset var qryResources = "" /> <cfset var rtnHTML = ""> <cfset var resourceGateway = variables.reactor.createGateway("opensourceresource") /> <!--- build a joined query of resources and resource categories ---> <cfset var objQry = resourceGateway.createQuery() /> <cfset objQry.join('opensourceresource','opensourceresourcecategories') /> <cfset objQry.join('opensourceresourceCategories','opensourcecategory') /> <cfset objQry.returnObjectFields("opensourceresource") /> <cfset objQry.returnObjectFields("opensourcecategory") /> <cfset objQry.getOrder().setAsc('opensourceresource','freeButNotOS')> <cfset objQry.getOrder().setAsc('opensourceresource','title')> <cfset objQry.getOrder().setAsc('opensourceresource','resourceID')> <cfif arguments.args[1] NEQ "all"> <cfset objQry.getWhere().isEqual('opensourcecategory','categoryID',arguments.args[1]) /> </cfif> <cfset qryResources = resourceGateway.getByQuery(objQry) /> <cfsavecontent variable="rtnHTML"> <cfif arguments.args[1] NEQ "all"><cfoutput><p><strong>Viewing Category: #qryResources.category#</strong> - [<a href="rss.cfm?categoryID=#qryResources.category#">RSS</a>] - [<a href="##" onclick="getResources('all');">View All Records</a>]</p></cfoutput></cfif> <cfoutput query="qryResources" group="resourceID"> <cfif qryResources.freeButNotOS EQ 1 AND qryResources.freeButNotOS[qryResources.currentRow -1] EQ 0><h3>Free But Not Open-Source</h3></cfif> <div id="resource"> <p><span class="title"><a href="#qryResources.href#">#qryResources.title#</a></span><cfif session.isLoggedIn> | <span class="edit"><a href="edit.cfm?resourceID=#qryResources.resourceID#">edit</a></span></cfif><br /> <span class="description">#qryResources.description#</span><br /> <cfif len(qryResources.license)> <span class="license">License: #qryResources.license#</span><br /> </cfif> <span class="categories">Categories: <cfoutput group="categoryID"> <a href="##" onclick="getResources('#qryResources.categoryID#');">#qryResources.category#</a> </cfoutput> </span> <p> </div> </cfoutput> </cfsavecontent> <cfreturn rtnHTML /> </cffunction> </cfcomponent>
First, it is important to remember that all your AJAX components need to extend the ajax.cfc. Second, as I have covered before, my list application is running on that post as I am not going to go over it here). Third, my application did use the onRequest() method in the Application.cfc, so I needed to put a different (basically empty) Application.cfc in the folder where my AJAX components reside.
Originally, my one and only method returned a query. AjaxCFC has alot of great built in features to handle query data. However, in the end, it seemed easier to me to generate this content via CF than spend alot of time rewriting this code in JavaScript. In fact, this simply duplicates the output code on my cfm page - another note: I decided not to call this AJAX function to get the full list on document load because this would have been slower, totally unnecessary and (I believe though I am not certain) could have issues with search engines; the only issue with this is the fact that the output code ends up duplicated. In the end, this function is much simpler than it may appear as all it does it perform a query and generate the HTML from that query which is then returned. The one thing is that AjaxCFC passes all arguments in an arguments array, which is why I refer to arguments.args[1], which in this case is the category id.
In my list display page, I created a couple of scripts to handle the AJAX calls:
<script type="text/javascript"> function getResources(id) { document.getElementById('resourceList').innerHTML = '<div align="right"><div style="width:100px;padding:3px;background-color:red;color:white;">loading...</div></div>'; DWREngine._execute(_ajaxConfig._cfscriptLocation,null,'getResources',writeList,id); } function writeList(result) { document.getElementById('resourceList').innerHTML =result; } </script>
The first script (getResources()) accepts the category id. The primary reason this script is necessary is to show some form of feedback to the user when they click a link (in the form of a loading box styled off gmail - surprise). That is what is occurring on the first line of this script. The second line is the AJAX call. The call passes the CF script location (which we defined earlier but could be defined explicitly here) as its first argument. The second argument (which is scriptName) is generally null. The third argument is the function to call within your cfc. The fourth argument is the JavaScript function to call when the data is returned. The last argument in this case is my category id argument. If I had more arguments to pass, I could continue to list these (and they would all be passed by the array). Strangely, in looking at my code I seem to have diverged from the documentation here as the docs show the arguments coming before the callback function - but weird...it works nonetheless.
The last script is very simple, it takes the returned result (a string) and sets the HTML of my DIV to the returned HTML.
So to call this I simply set the category links to:
<a href="##" onclick="getResources('#qryResources.categoryID#');">#qryResources.categoryName#</a>
That's it. Pretty simple right? Obviously there is alot more you can do with AjaxCFC, but hopefully this will serve as a decent introduction to AJAX newbies like myself. However, given its ease of implementation, I may delve into a little more AJAX in the future (trying hard not to overuse it as it is sometimes used purely for gimmick sake IMHO - and yes, the usage in the article is a little gimmicky, but the point was to learn :-)
Comments
So, I know everyone says ajaxCFC is easy, but I CAN'T GET IT TO WORK! I've put the above code into my document, but nothing but errors. Is there some place for specifc intructions? What else do I have to do other than what you've mentioned above?
Thanks for ANY help!
Posted By Bobby / Posted on 07/17/2006 at 10:25 AM
Ok, I would be happy to help. Keep in mind that this code is from a prior release. Several changes have been made to AjaxCFC since this was written that wold cause the above code to change. The best place for specific instructions are in the docs that come with AjaxCFC. I also wrote a much more recent article that used AjaxCFC for CFDJ and it is available at http://cfdj.sys-con.com/read/236003.htm - if these don't help let me know what kind of errors you are getting and I will do my best.
Posted By Brian Rinaldi / Posted on 07/17/2006 at 9:15 PM