I wrote a few sample applications for my jQuery presentation yesterday that I wanted to explore a bit deeper in a blog post. I think search is a great place for Ajax to help out. How can we build a search interface using jQuery and ColdFusion? Let's start with a simple example.
First I'll create a simple form with just a tiny bit of jQuery:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Untitled Document</title>
<script src="../jquery.js"></script>
<script>
$(document).ready(function() {
$("#searchForm").submit(function() {
//get the value
var search = $("#searchText").val()
if($.trim(search) == '') return false
$.get('search.cfm',{search:search},
function(data,status) {
$("#results").html(data)
})
return false
})
});
</script>
</head>
<body>
<form id="searchForm"><input type="text" id="searchText" /><input type="submit" value="Search" /></form>
<div id="results"></div>
</body>
</html>
Reading from the bottom up, you can see a simple form with one text field. The jQuery code handles taking over the submit action for the form. I first grab the value of the form field and then do a trim() on it. (Trim is something ColdFusion developers are used to and exists as a utility method in jQuery.)
The actual Ajax portion is done with the get call. The first argument is the code I'm going to hit: search.cfm. The second argument is a structure of name/value pairs. In this case I'm passing an argument named search and using the value from the form. The last argument to the get function is my call back, or, 'what to do when done'. In this case, I'm simply taking the results and stuffing it into the DIV with the ID of "results".
So to translate all of this into English: Get the form field. Pass it to search.cfm. Paste the result onto the page.
The ColdFusion code is trivial:
<cfparam name="url.search" default="">
<cfif len(trim(url.search))>
<cfset url.search = trim(htmlEditFormat(lcase(url.search)))>
<cfquery name="getArt" datasource="cfartgallery">
select artname, description, price
from art
where lower(artname) like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#url.search#%">
or description like <cfqueryparam cfsqltype="cf_sql_lomgvarchar" value="%#url.search#%">
</cfquery>
<cfoutput>
<p>
Your search for #url.search# returned #getArt.recordCount# result(s).
</p>
</cfoutput>
<cfoutput query="getArt">
<p>
<b>#artname#</b> #dollarFormat(price)#<br/>
#description#
</p>
</cfoutput>
</cfif>
I don't have much much going on here. I do some simple validation to ensure a search term was passed. If so, I do the query and just output the results. The CFM handles both the search and display of results.
Let's kick it up a notch. What if we wanted a more advanced search page? Here is a new version of the search page:
<cfquery name="mediatypes" datasource="cfartgallery">
select mediaid, mediatype
from media
</cfquery>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Untitled Document</title>
<script src="../jquery.js"></script>
<script>
$(document).ready(function() {
$("#searchForm").submit(function() {
//get the value
var search = $("#searchText").val()
if($.trim(search) == '') return false
var type = $("#mediatype option:selected").val()
$.post('search2.cfm',{search:search,mediatype:type},
function(data,status) {
$("#results").html(data)
})
return false
})
});
</script>
</head>
<body>
<form id="searchForm">
<input type="text" id="searchText" />
<select name="mediatype" id="mediatype">
<option value="">Any Media Type</option>
<cfoutput query="mediatypes">
<option value="#mediaid#">#mediatype#</option>
</cfoutput>
</select>
<input type="submit" value="Search" /></form>
<div id="results"></div>
</body>
</html>
What's different here? On top I did a quick query to get all the media types from the cfartgallery datasource. Once I have this data, I can use it in a select tag within the form (at the bottom of the code listing above). Now users can search both for a keyword and a keyword and a type of media.
The jQuery code changed a bit as well. Now I also get the selected value from the drop down and pass it in the Ajax call. Notice I switched to post as well. No real reason. In general I almost always prefer Post calls. I'm not going to post the code for search2.cfm as the only change was to look for and notice the mediatype value. (I'm including all of this in a zip attached to the blog entry though.)
Ok, one more example. In the previous two listings, ColdFusion handled running the search query as well as displaying the results. How about making this simpler? I'll just show the jQuery code for my third example since that's the only thing I'm going to change:
$(document).ready(function() {
$("#searchForm").submit(function() {
//get the value
var search = $("#searchText").val()
if($.trim(search) == '') return false
var type = $("#mediatype option:selected").val()
$.getJSON('art.cfc?method=search&returnFormat=json&queryformat=column',{term:search,mediatype:type},
function(result,status) {
//console.dir(result)
var str = ''
for(var i=0; i < result.ROWCOUNT; i++) {
str+= '<p><b>'+result.DATA.ARTNAME[i]+'</b> $'+result.DATA.PRICE[i]+'<br />'
str+= result.DATA.DESCRIPTION[i]+'</p>'
}
$("#results").html(str)
})
return false
})
});
So the first few lines are the same - notice the form submission, get the values, etc. Note that I've switched my Ajax call to getJSON. This let's jQuery know that I'll be calling something that returns JSON data. jQuery will handle converting the JSON for me into real JavaScript data.
Notice the URL I'm posting too:
art.cfc?method=search&returnFormat=json&queryformat=column
This is a CFC I've built to handle the search logic for me. I've passed returnFormat to tell ColdFusion to automatically convert the result into JSON.
A quick side note: I have both search parameters (term and mediatype) and url parameters (method, returnFormat, queryFormat). Could I mix them up differently? Yes. I could have used no URL parameters at all and put them all with the {}s in the second argument. I could have used an empty {} and put everything in the URL (with proper escaping of course). In my opinion, the form I used makes the most sense. I've kept the 'meta' stuff (how the request works) in the URL, separate from business logic params used in the second parameter.
Because I'm getting JSON back, I have to handle formatting the result myself. I worked with the result data to create a string and then simply set it to the results div using the html() function. How did I know how to work with the JSON data? Trust me, I had no idea. See this line?
//console.dir(result)
I removed the comments before the line and Firebug gave me a nice display of the data. This let me see how things were setup and let me write the rest of the code. Once again, install Firebug!
The CFC isn't too special. Here is the method I used:
<cfcomponent>
<cffunction name="search" access="remote" returntype="query">
<cfargument name="term" type="string" required="yes">
<cfargument name="mediatype" type="any" required="yes">
<cfset var getArt = "">
<cfquery name="getArt" datasource="cfartgallery">
select artname, description, price
from art
where (lower(artname) like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#arguments.term#%">
or description like <cfqueryparam cfsqltype="cf_sql_lomgvarchar" value="%#arguments.term#%">)
<cfif len(arguments.mediatype) and isNumeric(arguments.mediatype) and arguments.mediatype gte 1>
and mediaid = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.mediatype#">
</cfif>
</cfquery>
<cfreturn getArt>
</cffunction>
</cfcomponent>
What I love about this is that my CFC has nothing in it related to jQuery, Ajax, JavaScript, JSON, etc. The only clue that there is any Ajax stuff going on is the access="remote" argument. Because the JSON stuff is built into ColdFusion 8, I can write my business logic and use it either in my 'old school' Web 1.0 application or my fancy, multi-billion dollar Web 2.0 site.
That's it. Any questions?