Our Blog

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

Laura

Laura

49 Comments

  1. Anthony

    Anthony

    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.
  2. Anthony

    Anthony

    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?
  3. 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>
  4. Anthony

    Anthony

    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;
       }
    }
  5. 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;;
    }
  6. d4n
    Or
    if(value.indexOf(filterTerm) != -1 &amp;&amp; value.length &gt; 0)
  7. Joe
    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!!
  8. 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:
  9. 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.
  10. dave Lyons

    dave Lyons

    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 :)
  11. 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');
  12. dave
    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.
  13. James
    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?
  14. 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???
  15. 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;
  16. 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!
  17. Joanna

    Joanna

    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!
  18. Joanna

    Joanna

    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
  19. Joanna

    Joanna

    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
  20. Thomary

    Thomary

    Yes, I would like to see the code, please.
  21. Thomary

    Thomary

    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.
  22. 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;
    }
  23. Chad
    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.
  24. Ron Fuller

    Ron Fuller

    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.
  25. Al Foote

    Al Foote

    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
  26. 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)
  27. Stev Shaffer

    Stev Shaffer

    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>
  28. 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
  29. 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
  30. Steve
    Thanks Laura,

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

    Thanks for the response, and this site rocks!
  31. Natalie

    Natalie

    Thanks so much. This code really saved me alot of time. ;) Worked wonderfully.
  32. Kiley
    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
  33. 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.
  34. 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
  35. 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
  36. Mary
    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?
  37. tonny
    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
  38. tonny
    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
  39. william

    william

    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>
  40. Aki
    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>
  41. Scott
    I export a lot of reports to excel; is there anyway to only export the filtered data?
  42. simon
    If I have a large amount of data in the grid sometimes when I search several times the hour glass pops up and my search field looses focus.

    any ideas how i can force the focus back to the search field?
  43. Jim
    This does not work with database queries, where you have a datasource specified. For some reason this is a different type of structure than creating a query by scripting it.

    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.
  44. tony
    is it possible to use this function to filter a cfselect and not a cfgrid?
  45. Albert

    Albert

    Hi Laura,
    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>
  46. Albert

    Albert

    Hi Laura,
    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>
  47. Karen
    I've combined the filter function with a column of selectable check boxes. Is there a way gather the information from the selected rows outside of the grid? I would like to use that information elsewhere on the page and I was wondering if it was possible to pull that data out of the grid.

    Thanks,
    Karen