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?
Archived Comments
Very Nice ...
Happy Birthday ...
:)
Nice way of Celebrating your birthday... with a triple example. Hope we have many more happy birthdays to Ray!
Dumb question. I haven't ever fiddled with JSON/etc before, so I saw your examples and figured I give it a try.
I dumped everything onto our test box and received no results from the search. When I took a look using Firebug it was throwing a 404 error for the jquery.js template.
What am I missing?
Thanks!
Err.. this may be kind of obvious - but doesn't a 404 for jquery.js mean jquery.js is missing? It _should_ have been in the zip, if not, grab it from jquery.com, and ensure the code 'addresses' it (points to it) correctly.
Ray,
You are correct in your comment that it was obvious. I wasn't sure if it should have been included in the zip.
Let's put it this way-it wasn't in mine.
Thanks
That's weird. It should have been in the zip. If you see anything else missing, let me know. I may need to ...
oh wait.
I'm an idiot. I was thinking this blog entry was my presentation blog entry. That had the FULL zip. This entry just had a few files from it specific to the demo here.
Now it makes sense to me!
Sorry about that!
Hi Ray,
To add one obvious dumb comment on top of another...I hit the presentation entry (I watched part of it and will finish it later-very good stuff!) but didn't see the link for the full zip.
Where am I off now?
After watching (or before, or whatever) the preso, go to the embedded version on the blog entry, or @ SlideSix. Click the SS menu. There will be a button to download the attachment.
Ray,
I hate to even ask a browser compatibility question, but since I work for the government (and they strictly use Internet Explorer) I have no choice. I applied your example above (2nd example) to a form I have and it works beautifully in FireFox. It does nothing in IE. I did some debugging and found out that it is posing the form data and sending some data back, but it's basically a huge....blank. If I change $("#results").html(data) to $("#results").text(data) it will spit out all of the HTML is text form, but for some reason with it set to HTML I get blank results. Ever heard of this? I know IE can be problematic when it comes to DIVs, but I've Googled the hell out of this topic and can't find any love.
Hmm. jQuery is _supposed_ to just work in IE. Not having a nice IE to tet here, can you try two quick things for me?
First, try making the result html super simple. Just <b>IE SUCKS</b> and see if it works.
Secondly, um, I thought I had two things but I don't.
Firefox returns IE SUCKS, IE returns blank.
Changing html output to text output returns:
<b> IE SUCKS </b>
I just can't seem to figure out what its problem is with HTML. I really appreciate your help.
Just as a side note, if I copy the HTML that it spits out in text format and paste it into a new page under my site's root, it will display it fine with all the proper CSS elements. I'm thinking it has something to do with the DIV, but I'm grabbing at straws at this point.
Can I see this online anywhere?
I couldn't show the actual code for government security reasons, so I wrote up an example and posted it to the web. I was surprised to find that it worked on both browsers. Completely blew my mind. I moved my real code out to the web root and it also worked both places. I wrote a CSS class specifically for the div I'm dumping the html into, and suddenly everything fell into place. I guess it was just another one of IE's quirky CSS bugs. I really appreciate you taking the time to look at this for me and ultimately leading me to take that actions that helped me figure it out. THANK YOU RAY!!
No prob. Glad you got it!
Hi Ray,
I'm looking for the full download of the example above, you mention the presentation page, but I'm not finding it there either. Do you mind posting the zip here? I guess the download link above just as a subset of the files.
Thanks!
Chuck
Did you try the Download link at the end of the entry?
>>I was thinking this blog entry was my presentation blog entry. That had the FULL zip. This entry just had a few files from it specific to the demo here.
<<
So for the "presentation blog entry" I went here:
http://www.coldfusionjedi.c...
But maybe I'm just missing it, but don't see the download?
Thanks and Happy 4th!
Chuck
Oh, on the embed, click the Menu button. It's there.
Aha! Thank you!
Thanks Ray! I went from jQuery zero to hero in one day following your tutorials and recorded jQuery intro. Appreciate what you are doing for CF developers trying to get their heads around jQuery.
Glad to help!
What would it look like if all you wanted was to search a dropdown box alone on one field?
this is what i'm trying, and I can't figure it out:
<cfquery name="getArt" datasource="felony">
SELECT title, text, changesnotes, newnumber, discussion, id, statute.texasnumber, texas.texasnumber, statutename, keyword, link, category, prefix, texadcode, texadlink, chaptertitle, restorerights, licensekeyword
FROM statute INNER JOIN texas
ON texas.texasnumber = statute.texasnumber
where len(form.keyword) and isBinary(form.keyword) and form.keyword gte "aa"> and keyword = <cfqueryparam cfsqltype="cf_sql_lomgvarchar" value="#form.keyword#")>
Not sure I get what you mean. You want to "search" a drop down field?
Oh I get what you mean. Right now it REQUIRES you to type something. First get rid of this:
if($.trim(search) == '') return false
Then modify the CFC like so:
where 1=1
<cfif len(arguments.term)>
and (lower(artname) like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#arguments.term#%">
or description like <cfqueryparam cfsqltype="cf_sql_lomgvarchar" value="%#arguments.term#%">)
</cfif>
<cfif len(arguments.mediatype) and isNumeric(arguments.mediatype) and arguments.mediatype gte 1>
and mediaid = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.mediatype#">
</cfif>
I didn't test that - but it should work.
I think my question is simpler than that -- I've gotten test3 to work great; it's the addition of this section in search2demo that I can't get to work:
<cfif len(form.mediatype) and isNumeric(form.mediatype) and form.mediatype gte 1>
and mediaid = <cfqueryparam cfsqltype="cf_sql_integer" value="#form.mediatype#">
</cfif>
What would this cfif look like if all I needed was just one field called "keyword"?
If you just wanted keyword, then that code wouldn't be there at all.
Can you send to me this source code,please?
because i want to see demo this..
Thanks
There is a link to download the code. It's at the end of the main content.
Hi Ray,
I cannot get it work in IE 8. I see someone else has this problem. I added a CSS to the Div but it still doesn't work. Any ideas? Thanks!
You have to describe *how* it isn't working - exactly what. You may also want to modify this block:
$.post('search2.cfm',{search:search,mediatype:type},
function(data,status) {
$("#results").html(data)
})
to this:
$.post('search2.cfm',{search:search,mediatype:type},
function(data,status) {
$("#results").html(data)
},"JSON")
where we tell the code that the result is JSON.