GridViewFilterExtender : Filter GridView Rows Client-Side

Download Source Code and Sample application  : 

Excel's Filter Dialog My GridView Filter Extender
Excel_Filter    GridRowFilterExtender
   

         
The above Screen Shots look promising , don't they ?
So, How would one go about building such a control ? Well, I have it laid out in front of you .

1) Find all unique Strings in the same column
2) Build a lookup table that stores the Unique string and the row Indices for the rows that contain the Unique String
3) Once user builds a Filter Expression and clicks on "Ok" , Filter the Rows in the Table based on the Expression.
4) For Now , the Filter Expression can only be  a sequence of strings that the user wants to see in the grid.

Ex : If your grid looks like this :

GridViewSampleData

The filter expression would be a comma-separated string that contains the unique strings to match in the rows.

 var _currentRowFilter = "Ajax,Moo"

The lookup Table would look like this :

 _lookupTable
        Select All    "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23"    
        Ajax    "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16"    
        Moo    "17,18,19,20,21,22,23"    

so , if we want to filter the rows , we would need to : 

1) Hide all Rows to begin with .
HideRows : this.lookupTable["Select All"]

2) Show only those rows that are found in the lookup table against the filter Terms.
ShowRows :this.lookupTable["Ajax"]

That's about the algorithm / design for the extender to work .
Getting this working is another matter altogether.
Once you have understood this , lets start:

Find all unique Strings in the same column :

once you find all the unique strings in all subsequent rows for the same column , we will need to build the checkbox list that
will allows the user to select the filter Terms. I.e this : FilterExpressionSelector

This is a bit tricky , the way one would go about doing this would be :

               Find out which Column the Extender is added in .Find the Cell Index of the Column , lets say _cellIndex
               For all subsequent rows in the table, Lookup the innerText of the Cell with CellIndex cellIndex.

                  For Each Row
                    Read the innerText of the Cell at CellIndex cellIndex.
                    IF the string read is not yet present in the lookup Table then
                    BEGIN
                         add it to the lookup Table along with the rowIndex of the current Row
                    END
                   ELSE if string is already present in the lookupTable then

                    Begin
                       add the RowIndex to the lookupTable[string] location.
                   END IF

Once you have found all the Unique Strings , build the CheckBoxList and attach the appropriate handlers .

Once user builds a Filter Expression and clicks on "Ok" , Filter the Rows in the Table based on the Expression.

Hide All Rows .
Get all the Rows in the Table other than the header and the footer , _rowsInTable.
Get the Current Search Filter by adding all the strings that are checked in the CheckBoxList.
For each term in the Search Filter , get the appropriate row Indices ,
                  For each row Index in the Row Indices, _rowIndex
                  assign the cssClass _inFilterCssClass to the _rowsInTable[_rowIndex].

 

This is all neat , what if I have multiple columns in my GridView and I want to filter
each succeeding Column depending on the previous Column?
 

Well, I have thought about that too ..
This is what we would do :
Every FilterExtender (a) can have a Dependent Filter  (b) , if filter (b) is used to filter data ( it raises the itemFiltered Event ),
then  the Filter (a) has to rebuild its index to remove any index text
that was removed as a result of the filter expression on filter (b) .

Important Note about the DependentFilterID

If a FilterExtender (a)   has a FilterExtender (b)   as its Dependent Filter ID , they both should have the matching values for the
InFilterCssClass and the NotInFilterCssClass attributes.

Set DependentFilterID
In markup
 <Raj:GridViewFilterExtender  DependentFilterId="optionListPnl"
JavaScript
 $find(<filterBehaviorID>).set_dependentFilterId(<dependentFilterId>)

Attributes :

Name Description
TargetControlID ID of the control upon clicking on which , the Filter is shown
OkButtonId ID of the control upon clicking on which , the FilterExpression is applied and the filter is hidden
CancelButtonId ID of the control upon clicking on which , the FilterExpression is NOT applied and the filter is hidden
EnableSelectAll If the filter contains an option to "Select All" Rows
InFilterCssClass The CssClass applied to all rows which are available from the Filter Expression.
NotInFilterCssClass The CssClass applied to all rows which are NOT available from the Filter Expression.
FilterPanelID The Panel Control which contains the Filter

Events :

Name Description
itemSelected When any filter Expression is Selected
itemFiltered When the filter Expression is applied.
onOk when the Control Referenced by OkButtonId is clicked
onCancel when the Control Referenced by CancelButtonId is clicked

Subscribing to the Events :
a) Markup :

 <Raj:OptionPickerBehavior runat="server" ID="optionListPnl" TargetControlID="btnToggle"
         blah blah blah
         OnOkScript="okClickHandler"
         OnCancelScript ="cancelClickHandler"
         OnClientItemFiltered ="itemFilteredHandler"
         OnClientItemSelected ="itemSelectedHandler">
</Raj:OptionPickerBehavior>
 <script language="javascript" type="text/javascript">
            function itemSelectedHandler(element,args)  {
                Sys.Debug.trace( args.get_text() + "   " + args.get_isChecked());
            }
            function itemFilteredHandler(element,args)  {
                Sys.Debug.trace( args.get_item() + "   " + args.get_filterText());
            }
            function okClickHandler(element,args)  {
                Sys.Debug.trace( "Ok Button was clicked" );
            }
             function cancelClickHandler(element,args)  {
                Sys.Debug.trace( "Cancel Button was clicked");
            }
            function traceCall(msg) {
                $get("traceDiv").appendChild(document.createTextNode(msg)); 
            }
 </script>
 b) Script 
 $find("BehaviorId").add_onOk( okClickHandler );

$find("BehaviorId").add_onCancel( cancelClickHandler );

$find("BehaviorId").add_itemFiltered( itemFilteredHandler );

$find("BehaviorId").add_itemSelected( itemSelectedHandler );
 That's it for now ! Do give feedback on the control if you get a chance to use it / look at it .