Filtering a grid as you type - using functions
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
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.
Anthony
Anthony
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 "added" 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
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
My fix for this function is to test the length of "value" after you set it to item[columns[j]]
if(value.length > 0) {
if(value.indexOf(filterTerm) != -1) {
filteredData.push(item);
added = true;
}
}
Nahuel
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] + "\n";
}
d4n
if(value.indexOf(filterTerm) != -1 && value.length > 0)
Joe
thanks!!
Dan
Laura
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
onchange="getUrl('profile.cfm?ProStaffID=' + showprostaff.dataProvider[showprostaff.selectedIndex]['ProStaffID']);"
tia :)
Laura
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
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="getURL(""javascript:NewWindow=window.open('profile.cfm','Title','width=400,height=400,left=20,top=20');"")"
but in most browsers that changes the parent page to the js function instead of staying put.
James
applyFilter(one.text,grid,['filelabel','filename']);
applyFilter(two.text,grid,['filelabel','filename']);
Any ideas on how to do this?
Javier Julio
Laura
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
Muchisimas gracias Laura. Que te cuidas. Feliz Navidad!
Joanna
Any ideas? Thanks in advance!
Joanna
Thanks again.
Joanna
Joanna
Joanna
Thomary
Thomary
Please.
Thanks very much.
Javier Julio
responseHandler.onResult = function( results: Object ):Void {
//when results are back, populate the cfgrid
yourGrid.dataProvider = results.items;
}
Chad
Ron Fuller
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
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
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
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
Thanks
Laura
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
It was the case isssue after all. Man those things are annoying!
Thanks for the response, and this site rocks!
Natalie
Kiley
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
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
Laura
Forta has something similar to this in Flex 2: http://www.forta.com/blog/index.cfm/2006/7/13/Filtering-Data-In-Flex
George
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
Steve Walker
Any chance you will post the Flex 2 equivalents to these cfform examples?
tonny
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
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
<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
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
simon
any ideas how i can force the focus back to the search field?
Jim
To get this to work with a database query I had to iterate over the query and add the rows and columns to a created query.
tony
Albert
This wery good and usefull exaple.
I am trying to modify it.
With type functions I also need to filter grid by year from <cfselect></cfselect> and I stuck, here my code.Could you please help me.
<cfdirectory directory="#application.installPath##application.PDFfilesDir#" name="memberList" action="LIST" sort = "directory ASC">
<cfquery dbtype="query" name="reportsQuery">
SELECT distinct Name
FROM memberList
ORDER BY 1
</cfquery>
<cfform name="myForm" format="flash" height="320" width="700" skin="haloSilver" timeout="100">
<cfformitem type="script">
function applyFilter( term:String, grid:mx.controls.DataGrid, columns:Array ):Void {
var filterTerm:String = term.toString().toLowerCase();
if(filterTerm.length > 0) {
//alert(grid.id);
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];
}
}
</cfformitem>
<cfformgroup type="hbox">
<cfformgroup type="panel" label="Search in custom column">
<cfformgroup type="horizontal">
<cfinput type="text" name="secondTerm" onchange="applyFilter(secondTerm.text,contactList,['Name'])" width="90" label="Filter by:">
<cfselect name="column" label="in:" onchange="secondTerm.text=''" width="100" rowheaders="false">
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
</cfselect>
</cfformgroup>
<cfgrid name="contactList" query="contactsQuery" rowheaders="false">
<cfgridcolumn name="Name" header="Reports PDF">
<cfgridcolumn name="DateLastModified" header="Date Created">
<!--- <cfgridcolumn name="year" header="year"> --->
</cfgrid>
</cfformgroup>
</cfformgroup>
</cfform>
Albert
This wery good and usefull exaple.
I am trying to modify it.
With type functions I also need to filter grid by year from <cfselect></cfselect> and I stuck, here my code.Could you please help me.
<cfdirectory directory="#application.installPath##application.PDFfilesDir#" name="memberList" action="LIST" sort = "directory ASC">
<cfquery dbtype="query" name="reportsQuery">
SELECT distinct Name
FROM memberList
ORDER BY 1
</cfquery>
<cfform name="myForm" format="flash" height="320" width="700" skin="haloSilver" timeout="100">
<cfformitem type="script">
function applyFilter( term:String, grid:mx.controls.DataGrid, columns:Array ):Void {
var filterTerm:String = term.toString().toLowerCase();
if(filterTerm.length > 0) {
//alert(grid.id);
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];
}
}
</cfformitem>
<cfformgroup type="hbox">
<cfformgroup type="panel" label="Search in custom column">
<cfformgroup type="horizontal">
<cfinput type="text" name="secondTerm" onchange="applyFilter(secondTerm.text,contactList,['Name'])" width="90" label="Filter by:">
<cfselect name="column" label="in:" onchange="secondTerm.text=''" width="100" rowheaders="false">
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
</cfselect>
</cfformgroup>
<cfgrid name="contactList" query="contactsQuery" rowheaders="false">
<cfgridcolumn name="Name" header="Reports PDF">
<cfgridcolumn name="DateLastModified" header="Date Created">
<!--- <cfgridcolumn name="year" header="year"> --->
</cfgrid>
</cfformgroup>
</cfformgroup>
</cfform>
Karen
Thanks,
Karen