The following post will explain the logic behind the ‘live’ search results that are becoming more and more popular on the web. The idea is that you have a record set at hand ready to be searched as a user types into an input box what they are looking for. While in traditional software programming this has never been a huge deal, the request/response nature of the web makes our lives as web developers a little more difficult. To bypass the request/response model we let the searching happen via the client using AJAX.
For this example three files will be needed. The client file (the file with the filter tool and search results), a javascript file to handle the AJAX and jQuery output, and a Coldfusion component to handle the data processing. I’ve set up a demo here to check out so you’ll know what we’ll be building.
Let’s take a look at the client file first.
jquery_result_filter.cfm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!--- PROXY ---> <cfajaxproxy cfc="path.to.jquery_result_filter" jsclassname="filter"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Untitled Document</title> <!-- JQUERY LIBRARY --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script> <!-- JQUERY_RESULT_FILTER.JS --> <script src="js/jquery_result_filter.js" type="text/javascript"></script> </head> <body> Filter: <input type="text" name="filter" id="filter" value="" /><br /><br /><br /> <p>Data:</p> <div id="output"></div> </body> </html>
We have here a a proxy at the top that allows us to make asynchronous AJAX calls to the Coldfusion server. For javascript we load in the jQuery library and our javascript file that handles the AJAX calls and the data output to the DOM. In the body there is an input field which serves as our filter and an empty div which will have data loaded into it dynamically using jQuery.
Let’s take a look at our component to see how the data is being processed as the user types their filter.
jquery_result_filter.cfc
<cfcomponent displayname="jQuery Result Filter" hint="Manages filtering results using jquery." output="false">
<!--- *************************** FILTER DATA *************************** --->
<cffunction name="filterData" displayname="Filter Data" description="Filters the data to be returned." access="remote" output="false" returnformat="JSON" securejson="true" returntype="query">
<!--- PASS IN FILTER STRING --->
<cfargument name="filter" displayName="Filter" type="string" hint="String by which to filter results." required="false" default="" />
<!--- GET THE QUERY OBJECT --->
<cfset var querydata = generateQuery()>
<!--- FILTER QUERY ACCORDING TO THE FILTER STRING PASSED IN --->
<cfif ARGUMENTS.filter neq "">
<cfquery name="querydata" dbtype="query">
SELECT color_id, color
FROM querydata
WHERE color LIKE <cfqueryparam value="%#ARGUMENTS.filter#%" cfsqltype="cf_sql_varchar">
</cfquery>
</cfif>
<!--- RETURN RESULTS --->
<cfreturn querydata>
</cffunction>
<!--- *************************** GENERATE QUERY *************************** --->
<cffunction name="generateQuery" displayname="Generate Query" description="Generates a query of some random words." access="private" output="false" returntype="query">
<!--- CREATE A COUNTER TO KEEP UP WITH CURRENT LOOP --->
<cfset var counter = 1>
<!--- CREATE LIST OF RANDOM COLORS --->
<cfset var data = "black, blue, red, purple, green, yellow, magenta, white, cyan, brown, beige, tan, pink, orange">
<!--- CREATE A QUERY --->
<cfset var query = querynew("color_id, color", "Integer, VarChar")>
<!--- LOOP OVER LIST --->
<cfloop list="#data#" index="VARIABLES.i">
<!--- CREATE A NEW ROW FOR THE QUERY --->
<cfset queryaddrow(query, 1)>
<!--- SET VALUES INTO CELLS --->
<cfset querysetcell(query, "color_id", counter)>
<cfset querysetcell(query, "color", VARIABLES.i)>
<!--- INCREMENT COUNTER BY 1 --->
<cfset counter = counter + 1>
</cfloop>
<!--- RETURN QUERY --->
<cfreturn query>
</cffunction>
</cfcomponent>
Generally speaking we only have two functions here. We have a remote function that is called via AJAX each time the user presses a key in the filter field, and because I’m not working off an actual database here I have another function that creates a query of colors to be filtered. So looking at our primary method called ‘filterData’ we notice a couple really important things. First is that the access for our function is ‘remote’. This is required to call the method with AJAX. Because of this you should just consider the security issues involved. A simple and quick help on the security lines is to simply secure the returned JSON (assuming your returning JSON) that will by default prefix the returned data with the javascript single line comment ‘//’. Just remember that anything you return with this function will be visible to the caller via something like Firefox’s Firebug or Safari’s Web Devleoper. That said we’ll also notice that the function takes one argument – the string by which the filter is to be run. This argument is what the user has typed into the filter text field.
Moving on we call our private method ‘generateQuery’ which will generate a query of colors consisting of two columns – ‘color_id’ and ‘color’. Once we have a query to run the filter on we can check if the user has entered any kind of filter data. If not we obviously want to return all the results. If the user has passed in a filter string we need to run the filter on the query. Just a note that if your running your query off an actual database you’ll need to change my ‘dbtype’ to ‘datasource’. I’m using the ‘dbtype’ since I’m running a query of queries here. Some basic SQL using ‘WHERE color LIKE ‘%[FILTERSTRING]%’ (in our case ARGUMENTS.filter) the large record set will be narrowed down to just those records that are like whatever the user is looking for.
We now have a way of getting the data (our client side form and filter) and a way of processing the data (our Coldfusion component), but we still need a way of passing the data to be processed from the client to Coldfusion and a way of passing the processed data from Coldfusion back to the client. This is where we will use AJAX to pass the data around and jQuery to display the data dynamically to the user. Let’s take a look at the javascript file that handles all of this.
jquery_result_filter.js
$(document).ready(function()
{
//run the getdata() method when page loads
getdata("");
//listen for the keyup event on the filter input field
$("#filter").unbind().keyup(function()
{
//get the filter string
var filterstring = $(this).val();
//run getdata() method
getdata(filterstring);
});
});
getdata = function(filterstring)
{
//open proxy
var data = new filter();
data.setCallbackHandler(outputdata);
data.setErrorHandler(errorhandler);
data.filterData(filterstring);
}
outputdata = function(resp)
{
//empty the our output html
$("#output").html("");
//get the length of the record set
var numOfColors = resp.DATA.length;
//check if any results were returned
if(numOfColors > 0)
{
//loop over colors and output
for(var i = 0; i < numOfColors; i ++)
{
//set the color
var color = resp.DATA[i][1];
//set record number to begin at 1 and not 0
var recordnumber = i + 1;
//output the color
$("#output").append(recordnumber + ". " + color + "<br /><br />")
}
}
else
{
$("#output").html("No records were returned.");
}
}
errorhandler = function()
{
alert("problem running proxy");
}
Initially we need two things to happen. First, when the page loads we want to get all of our colors and show them to the user along with the filter box. Second, we want the user to type into the filter and have the results be filtered down as they type. We need to create a main method that will handle the ajax call to the Coldfusion component. Here this method is called ‘getdata()’, and takes the a string that contains the filter the user has specified in the text field. If you remember on the client page we opened an AJAX proxy at the top of the page. Using the jsclassname we gave it (‘filter’) we create a new instance, set our callback and error handlers, and lastly call the Coldfusion function in our component.
Again we need to run this getdata() method when the page loads and as the user types into the filter field.
Using jQuery’s $(document).ready() method we ensure that our code runs after the page has fully loaded. Once loaded we need to call the get getdata() method and pass an empty string as the filter. In our component we check if the filter is equal to an empty string in which case we do not run the filter. As such all results will be returned when the page loads. Now we need to deal with the user filtering.
In the document.ready() method we listen for the ‘keyup’ event (to learn more about why ‘keyup’ is used specifically read this post). When a keyboard key is released we run the getdata() method, but first we get the value of the filter field to use as the filter string. This time when we run the getdata() method we pass in the value that the user is filtering for.
Now when Coldfusion processes the data it will do so according to what the user has implied they want to see. The data is passed back as JSON to the outputdata() method. This method just outputs the results using some standard jQuery and javascript to do so. Because we are writing our data to the DOM dynamically with jQuery we left an empty div on the client page with an id of ‘output’. We are now going to populate this div with the returned data.
So first we want to wipe the slate clean by emptying the HTML from the ‘output’ div. Next we need to determine how many colors were returned. If no colors were returned we need to just give the user a message saying so, but if colors were returned we need to loop over each to output it. For each color we need to set the color and set the row number. Because javascript loops begin with zero we need to add 1 to each in order to make the list readable to the user. We do this by simply setting our ‘recordnumber’ var equal to the current loop (i) + 1. Lastly, just add the record to the ‘output’ div.
As the user filters the data the ‘output’ div will update to reflect what it is that they are searching for.
Hi there!
Just want to ask if there’s any substitute for cfajaxproxy. I’m using Coldfusion MX 6, and I don’t thing cfajaxproxy is available in this version, because I get an error that says that tag is unkonwn.
Thank you.
Jez
I’ll assume that if what you are trying to accomplish is remotely calling a cfc from a .cfm page then yes. I’ve never tried this with CF6, but I looked up the docs and it appears that the cffunction tag does contain a remote ‘access’ property. As such you could set your function tag to something like
Now you can use jQuery or any other method of AJAX in javascript. Since I’m most familiar with jQuery I’ll give an example using that here. Let’s say the ‘myFunction’ takes two properties: argumentOne and argumentTwo. Call the method via jquery as:
cffunction name=”myFunction” access=”remote”
$.ajax(type: “GET”,
url: “mycomponent.cfc”,
data: “method=myFunction&argumentOne=dataOne&argumentTwo=dataTwo”,
success: function(){…});
The jquery docs for this method can be viewed here.
Just as a side note you will need to make sure that you are returning either simple values, xml, or json. If you wish to return json you can do so using CF6. Not natively, but you can use jsonUtil to help assist in serializing complex data to json. I’ve used this with pre-cf8 applications I’ve worked on with success.
Hope this helps. Not as simple as later versions of CF, but still doable.
Hi Matthew! Thanks for your response.
I tried your suggestion but I could not get it working.
What I did was I put those lines of code (.ajax) in the js file replacing the getdata function. I know I have done some mistake coz it’s not working properly. When I run the code, I only get the textbox, but there’s no data shown (that is, the list of colors.)
Where should I put the .ajax that calls filterData function in jquery_result_filter.cfc? And should I include the data and success settings of jquery.ajax?
Actually, i’m just new to Coldfusion and Ajax. So, I apologize if my questions sound a bit stupid. =)
Thank you!
Jez
Hi, sorry for not responding sooner. And there’s no need to apologize, this is how we learn. So I had a chance tonight to convert over the above example to use the jquery .ajax() method.
First, since you are using CF6 you will want to change the returntype attribute of the filterData function to be a ‘String’. You will also want to remove the ‘returnformat’ and ‘securejson’ attributes in the function tag.
I’m sure you’ve done this already, but the cfajaxproxy tag will also need to be removed.
In the javascript replace the getdata() function with
getdata = function(filterstring)
{
$.ajax({
type: "POST",
url: "cfcs/jquery_result_filter.cfc",
data: "method=filterData&filter=" + filterstring,
datatype: "json",
success: function(data)
{
outputdata(data);
},
error: function()
{
errorhandler();
}
});
}
Also replace the outputdata() function with
outputdata = function(resp)
{
//empty the our output html
$("#output").html("");
var resp = eval('(' + resp + ')');
//get the length of the record set
var numOfColors = resp.DATA.color_id.length;
//check if any results were returned
if(numOfColors > 0)
{
//loop over colors and output
for(var i = 0; i < numOfColors; i ++)
{
//set the color
var color = resp.DATA.color[i];
//set record number to begin at 1 and not 0
var recordnumber = i + 1;
//output the color
$("#output").append(recordnumber + ". " + color + "")
}
}
else
{
$("#output").html("No records were returned.");
}
}
Essentially, I’m now returning data from the filterData() CF function as a json string. Convert the query into a json string you can use the jsonUtil() component I linked to above. I also had to change a little bit in the outputdata() JS function because the data is being returned slightly differently than when using the CF proxy tag.
Hope this helps. Let me know if you continue to have trouble.
Hey Matthew,
Thanks for your help. I tried using jSONUtil, but still it’s not working properly. This is how I called the JSONUtil.cfc:
I also tried using jsonencode.cfm from CFLib.org to serialize my query to json but still the same, I only get the textbox with no list of colors.
Where could the problem be? Thanks.