Using jQuery based jqGrid with ColdFusion: Part 2 - Filter Records & work with Custom Data

Continuing to work with jqGrid, this time we will implement its Filter/Search functionality. jqGrid provides an inbuilt filter component, which can be inbuilt into its navigation grid (navgrid), or from a custom pager/toolbar button, or from an external button on your page. This opens in the form of a window (similar to cfwindow):

Check Out the Demo Here

So first, we modify the JS from part I to include some more features. Lets see the code,
   view plainprintabout
 <script>
     $(document).ready(function()
         {
             $("#list").jqGrid(
             {
                 url:'Users2.cfc?method=getUsers', //CFC that will return the users
                 datatype: 'json', //We specify that the datatype we will be using will be JSON
                 colNames:['ID','First Name','Last Name', 'Display Name','User Name','User Accounting Code','Phone'], //Column Names
                 //The Column Model to define the data. Note you can make columns non sortable, specify width, alignment, etc.
10                  colModel :[
11                      {name:'id',index:'id', width:50, sorttype:"int",editrules:{integer:true}},
12                      {name:'FirstName',index:'FirstName', width:150, sorttype:"text"},
13                      {name:'LastName',index:'LastName', width:150, align:"left",sorttype:"text"},
14                      {name:'DisplayName',index:'DisplayName', width:150, align:"left",sorttype:"text"},
15                      {name:'UserName',index:'UserName', width:150,align:"left",sorttype:"text"},
16                      {name:'UserAccountingCode',index:'UserAccountingCode', width:150, sortable:false},
17                      {name:'Phone',index:'Phone', width:175, sortable:false,search:false}
18                  ],
19                  pager: $('#pager'), //The div we have specified, tells jqGrid where to put the pager
20                  rowNum:20, //Number of records we want to show per page
21                  rowList:[20,30,40,50], //Row List, to allow user to select how many rows they want to see per page
22                  sortorder: "asc", //Default sort order
23                  sortname: "ID", //Default sort column
24                  viewrecords: true, //Shows the nice message on the pager
25                  imgpath: '/Test/themes/basic/images', //Image path for prev/next etc images
26                  caption: 'Users', //Grid Name
27                  height:'auto', //I like auto, so there is no blank space between. Using a fixed height can mean either a scrollbar or a blank space before the pager
28                  mtype:'GET',
29                  recordtext:'Total Records', //On the demo you will notice "7 Total Records" - The Total Reocrds text comes from here
30                  pgtext:'/', //You notice the 1/3, you can change the /. You can make it say 1 of 3
31                  editurl:"Users2.cfc?method=getUsers", //Not used right now.
32                  toolbar:[true,"top"], //Shows the toolbar at the top. We will use it to display user feedback                 
33                  //Things to do when grid is finished loading
34                  loadComplete:function(){
35                          //We get the Userdata for the grid.
36                          var recorddata = $("#list").getUserData();
37                          //If the msg type is error, we do some CSS and change text color to red, otherwise its blue
38                          if(recorddata.TYPE == "Error"){
39                              $("#t_list").css("color","red")
40                          }else{
41                              $("#t_list").css("color","blue")
42                          }
43                          //Display the message in the toolbar
44                          $("#t_list").html(recorddata.MSG)
45                                                  
46                      },
47                  //The JSON reader. This defines what the JSON data returned from the CFC should look like
48                  jsonReader: {
49                      root: "ROWS", //our data
50   page: "PAGE", //current page
51   total: "TOTAL", //total pages
52   records:"RECORDS", //total records
53                      userdata:"USERDATA", //Userdata we will pass back for feedback
54                      cell: "", //Not Used
55                      id: "0" //Will default to frist column
56                      }
57                  }
58              );            
59              //We define global settings for the search grid. This makes the caption for the window to "Filter",
60              //The submit button is called "Search", width set to 400 for better viewing,
61              //and we checkInput based on the Edit Rules specified in colModel
62              $.extend($.jgrid.search,{caption:"Filter",Find:'Search',width:'400',checkInput:true});
63              //Here we use the navGrid to display the Search button with label Filter, and disable the add/edit/delete buttons.
64              $("#list").navGrid("#pager",{edit:false,add:false,del:false,searchtext:"Filter"});
65              
66              
67          }
68      );
69      </script>
The first major change was specifying the EditRules for the ID column, editrules:{integer:true}. This is used for both jqGrid's inbuilt edit functionality and the filter. This means, if the user enters alphabetical characters for an ID search, the call to our CFC won't happen. We are now also using the toolbar to display user data. Userdata is customdata that we need to display or use that is not a part of the standard grid JSON return.
   view plainprintabout
 loadComplete:function(){
 
 //We get the Userdata for the grid.
 var recorddata = $("#list").getUserData();
 //If the msg type is error, we do some CSS and change text color to red, otherwise its blue
 if(recorddata.TYPE == "Error"){
     $("#t_list").css("color","red")
 }else{
  $("#t_list").css("color","blue")
10  }
11  //Display the message in the toolbar
12  $("#t_list").html(recorddata.MSG)
13  
14  }
Over here on loadComplete, we get the UserData for the grid. In JSON, the userdata return looks like userData = {TYPE:here,MSG:text}. We then use some jQuery methods to set the toolbar CSS and its html message. Doing this is going to allow us to display errors when they occur during the Filter. The way the search paramters are passed to the CFC are, there is a _search parameter set to true when user uses the filter. There is the searchField, searchOper and searchString. The searchField is the field we are searching on (ID, FirstName, etc), the searchString is the search value. The search Operator is the operand, and can be:

eq - equal ( = )
ne - not equal ( <> )
lt - less than ( < )
le - less than or equal to ( <= )
gt - greater than ( > )
ge - greater than or equal to ( >= )
bw - begins with ( LIKE val% )
ew - ends with (LIKE %val )
cn - contain (LIKE %val% )

So, first I wrote a function in the CFC to build the search string.
   view plainprintabout
 <cffunction name="buildSearchString" access="private" hint="Returns the Search Opeator based on Short Form Value">
 <cfargument name="searchField" required="no" default="" hint="Field to perform Search on">
 <cfargument name="searchOper" required="no" default="" hint="Search Operator Short Form">
 <cfargument name="searchString" required="no" default="" hint="Search Text">
 
     <cfset var searchVal = "">
     
     <cfscript>
     switch(Arguments.searchOper)
10      {
11      case "eq":
12          searchVal = "#Arguments.searchField# = '#Arguments.searchString#'";
13          break;
14      case "ne":
15          searchVal = "#Arguments.searchField# <> '#Arguments.searchString#'";
16          break;
17      case "lt":
18          searchVal = "#Arguments.searchField# < '#Arguments.searchString#'";
19          break;
20      case "le":
21          searchVal = "#Arguments.searchField# <= '#Arguments.searchString#'";
22          break;
23      case "gt":
24          searchVal = "#Arguments.searchField# > '#Arguments.searchString#'";
25          break;
26      case "ge":
27          searchVal = "#Arguments.searchField# >= '#Arguments.searchString#'";
28          break;
29      case "bw":
30          searchVal = "#Arguments.searchField# LIKE '#Arguments.searchString#%'";
31          break;
32      case "ew":
33      //Purposefully breaking ends with operator (no leading ')
34  
        searchVal = "#Arguments.searchField# LIKE %#Arguments.searchString#'";
35          break;
36      case "cn":
37          searchVal = "#Arguments.searchField# LIKE '%#Arguments.searchString#%'";
38          break;
39  }    
40              
41  
</cfscript>
42              
43  <cfreturn searchVal>
44      
45  </cffunction>
I purposefully introduce an error in the "ends with" operator. So if you do the Filter with that, you will see feedback going to the user indicating an error. Now, I also have to modify the getUsers method to run the search and also return the error message if there is an issue with the search.
   view plainprintabout
 <cffunction name="getUsers" access="remote" returnformat="json">
 <cfargument name="page" required="no" default="1" hint="Page user is on">
 <cfargument name="rows" required="no" default="10" hint="Number of Rows to display per page">
 <cfargument name="sidx" required="no" default="" hint="Sort Column">
 <cfargument name="sord" required="no" default="ASC" hint="Sort Order">
 <cfargument name="nd" required="no" default="0">
 <cfargument name="_search" required="no" default="false" hint="Whether search is performed by user on data grid">
 <cfargument name="searchField" required="no" default="" hint="Field to perform Search on">
 <cfargument name="searchOper" required="no" default="" hint="Search Operator Short Form">
10  <cfargument name="searchString" required="no" default="" hint="Search Text">
11          
12      <cfset var arrUsers = ArrayNew(1)>
13      <cfset var strMsg = "">
14      <cfset var strMsgType = "Success">
15      <cfset var records = "">
16      <cfset var blnSearch = false>
17      <cfset var strSearch = "">        
18          
19      <cfif Arguments._search>
20          <!--- Check search query to verify we will have no errors. --->
21          <cfset strSearch = buildSearchString(Arguments.searchField,Arguments.searchOper,Arguments.searchString)>
22          <cfset blnSearch = true>
23          <cftry>
24              <cfquery name="qryCheck" datasource="RIADemo" result="res">
25                  SELECT
26                      TOP 1 ID
27                  FROM
28                      Users
29                  WHERE
30                      #PreserveSingleQuotes(strSearch)#
31              </cfquery>
32              <cfcatch type="any">
33                  <!--- Had an error, we won't run the search --->
34                  <cfset blnSearch = false>                    
35                  <cfset strMsgType = "Error">
36                  <cfset strMsg = "There was an issue with the Search. An Error Report has been submitted to Support.">                    
37              </cfcatch>        
38          </cftry>
39      </cfif>
40          
41      <cfquery name="selUsers" datasource="RIADemo" result="res">
42          SELECT
43              ID, FirstName, LastName, DisplayName, UserName, UserAccountingCode, Phone
44          FROM
45              Users
46          <!--- Run search based on flag --->
47          <cfif blnSearch>
48              WHERE
49                  #PreserveSingleQuotes(strSearch)#
50          </cfif>
51          ORDER BY #sidx# #sord#                
52      </cfquery>
53          
54      <cfset records = selUsers>
55          
56          
57      <!--- Calculate the Start Position for the loop query.
58      So, if you are on 1st page and want to display 4 rows per page, for first page you start at: (1-1)*4+1 = 1.
59      If you go to page 2, you start at (2-)1*4+1 = 5 --->

60      <cfset start = ((arguments.page-1)*arguments.rows)+1>
61      
62      <!--- Calculate the end row for the query. So on the first page you go from row 1 to row 4. --->
63      <cfset end = (start-1) + arguments.rows>
64          
65      <!--- When building the array --->
66      <cfset i = 1>
67          
68      <cfloop query="records" startrow="#start#" endrow="#end#">
69          <!--- Array that will be passed back needed by jqGrid JSON implementation --->        
70          <cfset arrUsers[i] = [#ID#,#FirstName#,#LastName#,#DisplayName#,#UserName#,#UserAccountingCode#,#Phone#]>
71          <cfset i = i + 1>            
72      </cfloop>
73          
74      <!--- Calculate the Total Number of Pages for your records. --->
75      <cfset totalPages = Ceiling(records.recordcount/arguments.rows)>
76          
77      <!--- If we have been succesful, we will return the Total number of records we counted --->
78      <cfif strMsgType EQ "Success">
79          <cfset strMsg = "Retreived #records.recordcount# Records.">
80      </cfif>
81          
82      <!--- Set te user data. The JSON format expected back is, USERDATA : {type:value,msg:text} --->
83      <cfset userdata = {type="#strMsgType#",msg="#strMsg#"}>
84          
85      <!--- The JSON return --->
86      <cfset strReturn = {total=#totalPages#,page=#Arguments.page#,records=#records.recordcount#,rows=arrUsers,userdata=#userdata#}>
87          
88      <cfreturn strReturn>
89          
90  </cffunction>
The code has comments, so hopefully things are clear. But basically, the first thing we do, if the _search parameter is true, is run the SQL with one row/column selection for the search criteria. If an error is returned, we set the blnSearch flag to false, otherwise it is set to true. So, when we run our main user selection query, based on the flag we know whether to run the search or not. In addition, if we do enounter an error, we set the msgtype to error and pass in a message to be displayed to the user. The full sourcecode is available Here. Update: Setting the width of the Filter/Search bar to auto, causes an error the first time the button is clicked on IE7.
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
inj's Gravatar Hi!
Link for full sourcecode has broken - 404 error
# Posted By inj | 1/30/09 12:32 AM
Kumar's Gravatar Sorry, link to source was broken. Link updated.

http://www.coldfusion-ria.com/Test/Users_jqGridPar...
# Posted By Kumar | 1/30/09 12:39 AM
Jim's Gravatar To just pass a variable to the cfc do this:

$("#list").jqGrid({
url:'Users.cfc?method=GetAllCases&myacct=<cfoutput>#session.bizorg_objid#</cfoutput>', //CFC that will return the users
# Posted By Jim | 1/30/09 11:38 AM
Kumar's Gravatar Thanks for the information Jim. That will certainly be useful in the future.
# Posted By Kumar | 1/30/09 2:58 PM
Jim's Gravatar Very Nice. Your effort of using CF with jquery has really helped me along in my efforts to do the same.
# Posted By Jim | 2/2/09 4:46 PM
Shijo Thomas's Gravatar Jim,
Can I remove the search options from the search form form ???
# Posted By Shijo Thomas | 3/17/09 11:40 AM
Jim's Gravatar Shijo,

If you examine Kumar's search cfc, you should be able to customize your search criteria to your requirement. This is Kumar's blog and the credit should go to him for this CF example.

Jim
# Posted By Jim | 3/17/09 11:56 AM
Jim's Gravatar Kumar,

Currently the search is case sensitive. My manager wanted to know if a check box could be put on the form to toggle if the search is case sensitive or not.

Is this possible?
# Posted By Jim | 3/18/09 6:15 PM
Kumar's Gravatar Jim,

The main reason the search would be case sensitive would be because of Javascript being case sensitive or if the DB being used is case sensitive.
# Posted By Kumar | 4/3/09 12:23 PM
Bobby Tables's Gravatar ...but you're not actually doing pagination on the database-level.

With 10 million rows in the table, your cf-server-level "pagination" retrieves them all.

Instead of the 20 or so you want.

Is there some reason for this that I am not aware of?
# Posted By Bobby Tables | 4/21/09 2:32 PM
Shirley Index's Gravatar I think it's because he doesn't know how.

Here's a generic one based on flip-flop.
---
select top (#nPerPage#) *
from
   (
      select top (#nPerPage#) *
      from
         (
            select top (#nPerPage# * #nPage#) columns
            from recordsets
            where #filters#               
            ORDER BY #customOrder#
         ) as sub
         ORDER BY #customOrderInverted#
   ) as dub
ORDER BY #customOrder
---

Briefly, in order to pick 10 rows per page from page 90:
Pick first 90 rows
Flip them
Pick first 10 rows
Flop them

Specific solutions for specific databases will be more optimal, such as rownumber () for SQL Server.
Of course indexes on the orderable columns are required to avoid table-scans.
# Posted By Shirley Index | 4/21/09 4:16 PM
Kumar Shah's Gravatar @Shirley, thanks for the post.

@Bobby, the idea was to show how jQuery can be used. At the end of day how one chooses to retreive the data from the database is completely upto them. Things will always differ based on each individual project.
# Posted By Kumar Shah | 4/21/09 4:22 PM
Sue Lornitzo's Gravatar Okay I have tried several different jquery type grids. EVERY time I get a balnk grid with no data showing. I have spent hours looking at hundreds of posts and possible solutions and nothing has worked. I can access the cfc directly through browser and get the cf administrator okay. But no data shows in grids. We have a couple Linux machines and a couple window machines and the code works on some machines and not others. All CF is set up identical on all machines. Paths etc.. We initially thought different versions of apache was the problem, but upgrading apache didn't work. I am sooooo stumped. The grid appears to be loading, but no data appears. No errors are thrown... HELP!!!!
# Posted By Sue Lornitzo | 5/4/09 12:47 PM
Kumar Shah's Gravatar Sue,

Make sure Request Debugging is turned off for ColdFusion on the server.
# Posted By Kumar Shah | 5/7/09 3:42 PM
Joe's Gravatar Hi,
Great piece of code, it has helped me out 10 fold. I am having a problem though and wonder if there is something in coldfusion that can help me. When the grid first loads it displays the userdata in the toolbar, and i can even write an alert that shows pieces of the userdata showing up. But when i post add or edit, it doesn't display the response userdata from add or edit. What can I do to solve this?
# Posted By Joe | 7/21/09 2:26 PM
Kumar Shah's Gravatar @Joe, I beleive when you add or edit a record it does an auto refresh after that action is performed. The auto refresh might be overwriting your user data.
# Posted By Kumar Shah | 7/21/09 3:24 PM
Alex's Gravatar Hi,
i am stuck on the tutorial..i have started working on this and the first tutorial works just fine but when i try this second on i get a grid design but not content from my db... datasources are fine.. the problem come when in change the url: to the location of the users2.cfc.. i am i doing somthing wrong the path is correct and so are my SQL statements..
any help would be great...
# Posted By Alex | 10/14/09 8:35 PM
Kumar Shah's Gravatar Alex,

make sure the method specified in the url is correct to. Otherwise, you can contact me via email and send me the code and I can take a look at it.
# Posted By Kumar Shah | 10/15/09 11:51 AM
Alex's Gravatar Thanks for the prompt reply... i tried it on my Dev machine at home and it seems to work just fine. It might just be a problem with my path to the cfc (might be to long sont know if that would make a difference) but thats for the help and .....thanks for the code this will make things much easier for me..
# Posted By Alex | 10/15/09 1:18 PM
Matt Woodward's Gravatar A little late to this party but just came across this post today--if you're seeing a blank grid like Sue was, make sure you do two things:
1. Use Firebug to make sure you're actually getting data back

2. Check your case sensitivity! JavaScript is case-sensitive, and ColdFusion has a nasty habit of uppercasing struct keys and keys in JSON data. "firstName" is not the same as "FIRSTNAME" as far as JavaScript is concerned, so if you're using the wrong case in the grid code, you won't see any data.
# Posted By Matt Woodward | 12/18/09 1:55 PM
ceh's Gravatar I was really jazzed when I came across these examples. Part 1 works just fine, but wen I tried to use the latest version of jQuery 1.3.2 & jqGrid 3.6.3 for searching I just see "Loading" and no search icon in the bottom tool bar, like the example.

Thanks, and keep up the great work!
# Posted By ceh | 2/16/10 10:26 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.5.003.  Design based on ARCLITE by: digitalnature