Sep
27

Filtering a grid as you type - using functions

43 comments Posted by: Laura

With the CF Updater 7.01 we have now the ability to make our own functions, which are available from anywhere in our form. The biggest benefit is that the function will be ready to use as soon as the form loads. In the past, we could only add ActionScript code in the "on" handlers, such as the onclick of a button. That meant that we could not use the functions we declared until that button was clicked, or we had to resort to the onload hack. Moreover, declaring functions by the old method (myfunction = function(){}) required us to use _root to refer to our controls or some other scope workarounds (such as writing var myControl = myControl before the function definition) because the controls would not be in the function scope, making it very difficult to understand why some things would not work and creating really ugly code by using _root everywhere.

Declaring a function is as easy as:

<cfformitem type="script">
   function myFunction():Void{
      //do something

   }
</cfformitem>

I wanted to make a useful example on how to use functions, and since the “filtering as you type” series is very popular, I chose it for an “upgrade”. Note that this code is not using the “filtering as you type – revisited” code, which works for editable grids, so if you need to edit the grid, please refer to that post.

By using a function, we can generalize the filtering code, so that it works for multiple datagrids in the same form, or by different columns in the same grid very dynamically.

The idea is that we will have a function called applyFilter() to which we will send three parameters: the term we are filtering by, the grid on which we want to apply the filter, and the columns in which we want to search.

A call to this function looks like:

applyFilter(myInput.text,myGrid,['column1','column2']);

To put everything together, first, add an input text that will supply the search term, and on the onchange of this input, call the function:

<cfinput type="text" name="term" onchange="applyFilter(term.text,myGrid,['firstName','lastName'])" label="Filter by:">

In the example above, we will be searching only in the firstName and lastName columns.

This function can be called from as many different inputs as needed by supplying the corresponding parameters. Note that the column names parameter is an array of strings with the names of columns matching the exact case of the column query.

We also need a grid:

<cfgrid name="myGrid " query="contactsQuery" rowheaders="false">
   <cfgridcolumn name="firstName" header="First Name">
   <cfgridcolumn name="lastName" header="Last Name">
   <cfgridcolumn name="email" header="Email">
</cfgrid>

and finally the function:

function applyFilter( term:String, grid:mx.controls.DataGrid, columns:Array ):Void {

   var filterTerm:String = term.toString().toLowerCase();

   if(filterTerm.length > 0) {
      if(_global.unfilteredData[grid.id] == undefined){
         if (_global.unfilteredData == undefined){
            _global.unfilteredData = {};
         }
         _global.unfilteredData[grid.id] = grid.dataProvider.slice(0);
      }
      
      var filteredData:Array = [];

      for(var i = 0; i< _global.unfilteredData[grid.id].length; i++) {
         var item:Object = _global.unfilteredData[grid.id][i];
         var added:Boolean = false;
         
         for(var j = 0; j< columns.length; j++){
             if(!added){
               var value:String = item[columns[j]].toString().toLowerCase();
               if(value.indexOf(filterTerm) != -1)   {
                  filteredData.push(item);
                  added = true;
               }
            }
            else {
               break;
            }
         }
      }

   grid.dataProvider = filteredData;

   }
   else {
      if(_global.unfilteredData[grid.id] != undefined) grid.dataProvider = _global.unfilteredData[grid.id];
   }
}

Although the code can go anywhere in the form, I think it is better to put all the scripts at the top of the form, before the controls.

The zip for download contains the two examples shown in the live example page. The first uses all columns for searching and the second uses a drop down to get the name of the column to search. Both use the same function, declared once.

Live example

Download the source

Category: CFForm | ColdFusion |

43 Comments so far

Write yours
Anthony
1. Anthony wrote on September 29, 2005 at 4:49 PM
All your guys stuff is great! Just got this running locally, gotta run but i noticed blank fields seem to be found by the filter.
Anthony
2. Anthony wrote on September 30, 2005 at 9:03 AM
Well I've taken some time to try and understand the AS code and I came up with a question. How do you go about debugging your code?
Your example has two versions, the one where you search multiple columns is the one I'm having trouble with. I have, like your example the search box searching 3 columns. In some cases the columns have no data. If i type something very specific I'm stil getting the rows with the blank column showing up. I tried adding the alert function to the if statement where &quot;added&quot; is set to true to output value but when i do that the form locks up the browser as soon as i type. Have any ideas?

An alert is how I would normally debug something like this in javascript, it bothers me that the same technique crashes flash (8)

How do you go about debugging your flash CF forms?
Tim
One of the benefits of the cfsavecontent method was that you could put your actionscript in a separate file than the actual cfform code. I didnt want to add a cfformitem with type=script and plop all that as code into my cfform code.

Good news is that the #include works. I put all my AS code into one (or more) .as files, and include them. AS code cannot contain CF code, so any dynamic code needs to be handled another way.

<cfform name="orderForm" height="100%" width="100%" format="Flash">

<cfformitem type="script">
   <cfoutput>
   var gatewayURL = "CGI.HTTP_HOST#/flashservices/gateway/"
   </cfoutput>
   #include "as\MyactionScript.as"
</cfformitem>

</cfform>
Anthony
4. Anthony wrote on September 30, 2005 at 12:57 PM
Still interested in how you debug but...

My fix for this function is to test the length of &quot;value&quot; after you set it to item[columns[j]]
if(value.length &gt; 0) {
   if(value.indexOf(filterTerm) != -1)   {
      filteredData.push(item);
      added = true;
   }
}
Nahuel
Anthony,
Sometimes I just use the Alert if if a simple thing. Others I put a cftextarea and use it as my console, where I output everything.
A tip, if you are debugging an object you can make a for in loop to check their properties like
for( i in myObject)
{
myConsole.text += i + myObject[i] + &quot;\n&quot;;
}
d4n
6. d4n wrote on October 03, 2005 at 6:43 AM
Or
if(value.indexOf(filterTerm) != -1 &amp;&amp; value.length &gt; 0)
Joe
7. Joe wrote on October 05, 2005 at 8:00 PM
ive added a checkbox and made the grid editable. if i filter the data, check a row and submit the form, the form doesnt submit any data. it seems the FILTER action removes the values of the selected row. however, as long as the grid isnt filtered, it works perfectly. i dont understand why this would be happening.

thanks!!
Dan
I'm also experiencing the same problem. With the filter applied the grid no longer submittes its array to be processed but rather just regular form field submissions for the first record. Are there any work arounds for this:
Laura
Joe and Dan,
As it is mentioned in the post, you will need to change the code using this:

http://www.asfusion.com/blog/entry/filtering-a-cfgrid-as-you-type--revisited-

That was a little more difficult than what I wanted to show in this basic example, so that is why it was not explained in the post. If there is enough demand, I may add it.
dave Lyons
10. dave Lyons wrote on November 15, 2005 at 3:46 PM
how can i get the following code to make a popup window from the cfgrid?

onchange=&quot;getUrl('profile.cfm?ProStaffID=' + showprostaff.dataProvider[showprostaff.selectedIndex]['ProStaffID']);&quot;

tia :)
Laura
dave,
It is getURL(), not getUrl().
That code, however, will link to the profile page, not open a new window. You can use _blank or use a javascript popup.
getURL('myUrl', '_blank');
dave
12. dave wrote on November 17, 2005 at 12:57 AM
thanks laura
I know that code wouldnt do that popup but was thinking maybe someone would know. I been using the following but it has a few problems, one it only works on safari and opera and I cant seem to get the selected grid added on to it to pass on, the basic code sans the selected grid is this
onchange=&quot;getURL(&quot;&quot;javascript:NewWindow=window.open('profile.cfm','Title','width=400,height=400,left=20,top=20');&quot;&quot;)&quot;

but in most browsers that changes the parent page to the js function instead of staying put.
James
13. James wrote on December 01, 2005 at 8:21 AM
What if I wanted to have a drop down for a companyname and text field for descriptions. Is there a way to have to fields to search the grid. I tried putting this in the both of the fields onchange attribute but it doesn't seem to work:

applyFilter(one.text,grid,['filelabel','filename']);
applyFilter(two.text,grid,['filelabel','filename']);

Any ideas on how to do this?
Javier Julio
The applyFilter() only works on a cfgrid that uses the query attribute. I've noticed this because I have other grids that are only populated when you have selected something from another grid. Is there anyway to use applyFiler() with a cfgrid that does not use the query attribute but is populated via a query but through remoting instead???
Laura
James,
You will have to modify the function to do what you want because every time the function is called it uses the original grid data.

Javier,
If you use remoting, you need to set your dataprovider as
myGrid.dataProvider = results.items;
Javier Julio
Friggin brilliant!! Laura you do some amazing work. :) I put in .items in my remoting calls for 2 grids and now the applyFilter works. Truly amazing!

Muchisimas gracias Laura. Que te cuidas. Feliz Navidad!
Joanna
17. Joanna wrote on January 08, 2006 at 11:05 AM
Hi, this is great suff, and I am now using it. The only trouble I am having is when I type a space in the filter. If I do that, the dataProvider seems to go away and no results come back after I remove all of the filter term. I thought I had seen a comment and fix for this but can't seem to find it again.

Any ideas? Thanks in advance!
Joanna
18. Joanna wrote on January 08, 2006 at 11:10 AM
Just an addendum to my last post, what is actually happening is that if i type more than 1 letter, and then erase my filter term, the dataProvider does not go back to the original saved backup.

Thanks again.
Joanna
Joanna
19. Joanna wrote on January 08, 2006 at 11:18 AM
Last comment, swear :-)...the fix for the problem I was having (which has a function refresing the grid) is to move the _global.unfilteredData object to the function where I actually return the data. This keeps only the inital returned query rather than resetting it everytime a letter is added to the filter term....if anyone would like to see the code, let me know!

Joanna
Thomary
20. Thomary wrote on January 17, 2006 at 1:38 PM
Yes, I would like to see the code, please.
Thomary
21. Thomary wrote on January 17, 2006 at 1:42 PM
Javier, I am trying to get two grids going. could you explain where to update the action script with myGrid.dataProvider = results.items;
Please.
Thanks very much.
Javier Julio
Thomary, in the function where you are remoting to get a query from CF to populate a grid in Flash (if you are not using the query attrubute of cfgrid) what you need to do is make sure you have this in the onResult:

responseHandler.onResult = function( results: Object ):Void {
   //when results are back, populate the cfgrid
   yourGrid.dataProvider = results.items;
}
Chad
23. Chad wrote on February 07, 2006 at 7:48 AM
Does anyone have an example of how to move more than one selectedIndex of one cfgrid to another cfgrid? I've tried and tried, but yet have to solve it.
Ron Fuller
24. Ron Fuller wrote on April 28, 2006 at 9:48 PM
I couldn't get the desired functionality until I modified the code like this:
var pos:Number = value.indexOf(filterTerm);
if( !isNaN(pos) && pos >= 0) {
filteredData.push(item);
added = true;
}
Otherwise it was adding rows to the filter that had one of the characters in the filterTerm but not all.
Al Foote
25. Al Foote wrote on April 29, 2006 at 7:09 PM
Wow -- AS Fusion is my new favorite place! I have a suggestion (disguised as a question) and a question.

First, to Tim (9/30/05) couldn't you put the code in a .cfm file and cfinclude that? That's what I did and it seems to work -- am I missing anything performance or security wise?

Now my real question -- Is there any way to choose the line of a grid (via a checkbox) and have those choices dynamically populate a second grid? It kind of seems like a combination of this function and the filter-as-you-type function in another post. I just can't wrap my brain around how to break them apart and put them back together again :) (You know, other than doing a LOT more studying of AS!)

It feels like what I want to do would be A LOT easier in Flex 2, but that's not an option right now because I'm on a Linux server, which the Flex/ColdFusion adaptor doesn't support yet.

Al
Laura
Ron,
That is by design. indexOf() does not look for exact matches. If you want exact matches, then you need to use ==.

Al,
Thank you!
Regarding your question... of course it is possible, but yeah, you would need to learn some AS. You would need to modify the filter to look for checked items only instead of checking for string matches.
I am pretty sure the Flex/CF adapter runs on Linux. Have you downloaded it? (just remember it is still in beta)
Stev Shaffer
27. Stev Shaffer wrote on May 17, 2006 at 11:27 AM
Hey Everyone,

I'm having issues with getting the dataprovider to pass the valuer to javascript in a getURL statement, it's trying to link but the section that posts the data from the grid is passed as 'undefined'.. my code is below, any help would be greatly appreciated... Thanks!

<CFFORM name="myform" format="flash">

<cfgrid name="SearchResults" width="400" query="Search"
onchange="getURL('article.cfm?article_id=' + SearchResults.dataProvider[SearchResults.selectedIndex]['key']);">
<cfgridcolumn name="key">
<cfgridcolumn name="score">
<cfgridcolumn name="category">
<cfgridcolumn name="title">

</cfgrid>

</CFFORM>
George
I have this working great, I am actually using a cfselect. How can I pre select the first selection; so the grid when loaded is already filtered to the current month? Im thinking something with the onload.

Thanks
Laura
Stev,
Have you tried using
SearchResults.selectedItem.key instead of SearchResults.dataProvider[SearchResults.selectedIndex]['key'] ? Also make sure your db column has the same case you are using for "key".

George,
You need to look at this post http://www.asfusion.com/blog/entry/knowing-when-the-cfform-data-arrives
Steve
30. Steve wrote on May 18, 2006 at 11:11 AM
Thanks Laura,

It was the case isssue after all. Man those things are annoying!

Thanks for the response, and this site rocks!
Natalie
31. Natalie wrote on June 08, 2006 at 11:46 AM
Thanks so much. This code really saved me alot of time. ;) Worked wonderfully.
Kiley
32. Kiley wrote on June 27, 2006 at 10:48 AM
This filtering is awesome! I've used it on several projects and customers are totally blown away. Thank you asfusion.com.

I have a small customizing problem:

I use a filtering ActionScript like this one: http://www.asfusion.com/blog/examples/cfforms/filterGrid.cfm

Then, I would like to use the "filter as you type" on the contents of the cfgrid.

Suppose I chose HR from the drop down, then I would like to filter out more people by typing characters in the text box.

The way it works now I get a fresh copy of my original data not filtered from the drop down.

I think it has something to do with this line:

<cfsavecontent variable="actionFilter">
if(_global.backupDP == undefined)
{
_global.backupDP = gridCustomers.dataProvider.slice(0);
}
I think this gets all the original data and then the rest of the script applies to that record set. Can I check if there is already data there and get the filter as you type to use that?

Many thanks!

Kiley
Laura
Kiley,
You are correct, when we first run the filter, we make a copy of the array so that we can go back to the original records when the user enters a different criteria.
In the function described in this post, you will have to use your already filtered array instead of _global.unfilteredData
I hope that helps.
George
How do you do this using FLEX 2; using Cold Fusion and the remoteObject.
Laura
George,
Forta has something similar to this in Flex 2: http://www.forta.com/blog/index.cfm/2006/7/13/Filtering-Data-In-Flex
George
Laura,
I know about that filter, and see some other variation but NONE like you perfect for my apps. Input text with combobox to control the column being text searched on. I have been trying for a week and can not do it. If you can help that would be great! Thanks for all you have already done for me.

George
Mary
37. Mary wrote on November 10, 2006 at 7:28 PM
This code worked great for me. I'd like to add a checkbox to the second grid (e.g. <cfgridcolumn name="isChecked" header="Check" type="boolean" width="45" />). When the first grid is selected, I'd like the filtered checkboxes to be automatically checked. This seems like a simple function. Can anyone give me a clue or two?
Steve Walker
Laura,

Any chance you will post the Flex 2 equivalents to these cfform examples?
tonny
39. tonny wrote on August 14, 2007 at 2:43 PM
Hi Laura,

I am having a problem in getting the filtering work. The grid did display except when I type in the text onto the cfinput text box, it does not filter ( nothing happens ).

Any suggestions, thanks
tonny
40. tonny wrote on August 14, 2007 at 3:21 PM
Hi Laura,

I did install and test the download source and it works. It is when I tried to set the cfgrid to a query

<cfgrid name="contactList2" query="myQuery" rowheaders="false">

myQuery is created by <cfquery>

Thanks

Toony
william
41. william wrote on November 08, 2007 at 6:08 AM
How can you filter the grid as you type like using this method in AS ??

<cfquery datasource="bookmark" name="result">
SELECT * FROM Favorite
WHERE User_name = '#form.userid#'
<CFLOOP list="#form.searchurl#" index="thisword" delimiters=" ">
AND (URL LIKE '%#thisword#%' OR details LIKE '%#thisword#%')
</CFLOOP>
Aki
42. Aki wrote on December 28, 2007 at 8:42 AM
Hi all...
I have a slight problem here, I am trying to use <cfquery> in cfm to populate data. I am using firstname, lastname and email fields to populate the grid. Grid shows all the info but then I try to type into the filter box nothing happens. I have not modified the cfm code. So my cfc looks like this:
<cfcomponent name="contacts" access="public">
<cffunction name="getAll" output="false" description="Returns all the contacts in the database"
               access="public" returntype="query">
   
<cfquery name="memberList" datasource="database">
select id, firstname, lastname, email
from passwords
order by firstname, lastname
</cfquery>   
<cfreturn memberlist>
</cffunction>
</cfcomponent>
Scott
I export a lot of reports to excel; is there anyway to only export the filtered data?

Leave your comment

Comment etiquette: As a gesture to those subscribed to this post, please keep your comments relevant to the post.

Your email address will never be displayed.
Email is gravatar enabled.Gravatar are the pictures you see next to the comments. If you like to have one, visit gravatar



Allowed tags:

<code>
All other tags will be shown as such, when in doubt, use the preview.




Preview:

Refresh Preview
1. You wrote on