As I’ve been swimming in the great jQuery lake I discovered the jQuery QuickSearch Plugin,

As WebForms developers we often display lots of data using a gridview control.

I though it would be neet to set up using the jQuery QuickSearch plugin with the Gridview.

Here’s how it works.

Ihave a GridView wired to some sample data. For demo simplicity I’m using an XMLDataSource and reading from an XML file in my App_Data directory.

When the user starts to type into the Filter Text Box the rows in the table are filtered, meaning rows that don’t match the character(s) entered are hidden from view.

As the user continues to type, the list is filtered further.

The ASPX Page Markup is as follows.


<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" 
         AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="Scripts/jquery.quicksearch.js" type="text/javascript"></script>
    <script type="text/javascript">
		    $(function () {
			    $('input#id_search').quicksearch('table#table_example tbody tr');
             })
    </script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET!
    </h2>
    <p>
        To learn more about ASP.NET visit 
        <a href="http://www.asp.net" title="ASP.NET Website">www.asp.net</a>.
    </p>

    <input id=id_search type=text placeholder="Search"><br /><br />
    <asp:XmlDataSource ID="productsDataSource" Runat="server" 
                       DataFile="~/App_Data/Products.xml">
    </asp:XmlDataSource>
    <asp:GridView ID="table_example" Runat="server" 
        DataSourceID="productsDataSource" AutoGenerateColumns="False" 
        ClientIDMode="Static" onprerender="table_example_PreRender">
        <Columns>
            <asp:BoundField HeaderText="ProductID" DataField="ProductID" 
                            SortExpression="ProductID"></asp:BoundField>
            <asp:BoundField HeaderText="ProductName" DataField="ProductName" 
                            SortExpression="ProductName"></asp:BoundField>
            <asp:BoundField HeaderText="QuantityPerUnit" DataField="QuantityPerUnit" 
                            SortExpression="QuantityPerUnit"></asp:BoundField>
            <asp:BoundField HeaderText="UnitPrice" DataField="UnitPrice" 
                            SortExpression="UnitPrice"></asp:BoundField>
            <asp:BoundField HeaderText="UnitsInStock" DataField="UnitsInStock" 
                            SortExpression="UnitsInStock"></asp:BoundField>
        </Columns>
    </asp:GridView>
</asp:Content>
       

On line #8 we select the textbox where the user will eneter text to serve as the filter criteria and then pass as an argument to the quicksearch method the items we are filtering. In our case table rows inside a table.

If we run the application at this point though, we will not get the results we desire. When we start filtering we see the column header row disapear.

Take special note of line #28, specifically the “onprerender” specifier.

This event handler exists in the .aspx.cs code-behind file :


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void table_example_PreRender(object sender, EventArgs e)
    {
        if (table_example.Rows.Count > 0)
        {
            table_example.UseAccessibleHeader = true;
            table_example.HeaderRow.TableSection = TableRowSection.TableHeader;
        }
    }
}


This addition to our code is very important in order to achieve the desired behavior.

In order to understand this we need to disect the markup that is emitted by our ASP.NET code.

First, lets look at the markup that is emitted by the GridView WITHOUT the prerender event handler.


<table cellspacing="0" rules="all" border="1" id="table_example" 
       style="border-collapse:collapse;">   
   <tr>
      <th scope="col">ProductID</th>
      <th scope="col">ProductName</th>
      <th scope="col">QuantityPerUnit</th>
      <th scope="col">UnitPrice</th>
      <th scope="col">UnitsInStock</th>
   </tr>
   <tr>
      <td>1</td>
      <td>Chai</td>
      <td>10 boxes x 20 bags</td>
      <td>18.0000</td>
      <td>39</td>
   </tr>
   <tr>
      <td>2</td>
      <td>Chang</td>
      <td>24 - 12 oz bottles</td>
      <td>19.0000</td>
      <td>17</td>
   </tr>
   <tr>
      <td>3</td>
      <td>Aniseed Syrup</td>
      <td>12 - 550 ml bottles</td>
      <td>10.0000</td>
      <td>13</td>
   </tr>
   <tr>
      <td>4</td>
      <td>Chef Anton's Cajun Seasoning</td>
      <td>48 - 6 oz jars</td>
      <td>22.0000</td>
      <td>53</td>
   </tr>
   <tr>
      <td>5</td>
      <td>Chef Anton's Gumbo Mix</td>
      <td>36 boxes</td>
      <td>21.3500</td>
      <td>0</td>
   </tr>
   <tr>
      <td>6</td>
      <td>Baked Beans</td>
      <td>48 - 6 oz jars</td>
      <td>12.0000</td>
      <td>43</td>
   </tr>
   <tr>
      <td>7</td>
      <td>Buffelo Wings</td>
      <td>12 per box</td>
      <td>14.0000</td>
      <td>143</td>
   </tr>
</table>

Note that the headers, the “TH” elements live inside a standard table row “TR”.

Let’s now look at the HTML emitted by the GridView AFTER we add the preinit event handler listed above.


<table cellspacing="0" rules="all" border="1" id="table_example" 
       style="border-collapse:collapse;">
   <thead>
      <tr>
         <th scope="col">ProductID</th>
         <th scope="col">ProductName</th>
         <th scope="col">QuantityPerUnit</th>
         <th scope="col">UnitPrice</th>
         <th scope="col">UnitsInStock</th>
      </tr>
   </thead>
   <tbody>
      <tr>
         <td>1</td>
         <td>Chai</td>
         <td>10 boxes x 20 bags</td>
         <td>18.0000</td>
         <td>39</td>
      </tr>
      <tr>
         <td>2</td>
         <td>Chang</td>
         <td>24 - 12 oz bottles</td>
         <td>19.0000</td>
         <td>17</td>
      </tr>
      <tr>
         <td>3</td>
         <td>Aniseed Syrup</td>
         <td>12 - 550 ml bottles</td>
         <td>10.0000</td>
         <td>13</td>
      </tr>
      <tr>
         <td>4</td>
         <td>Chef Anton's Cajun Seasoning</td>
         <td>48 - 6 oz jars</td>
         <td>22.0000</td>
         <td>53</td>
      </tr>
      <tr>
         <td>5</td>
         <td>Chef Anton's Gumbo Mix</td>
         <td>36 boxes</td>
         <td>21.3500</td>
         <td>0</td>
      </tr>
      <tr>
         <td>6</td>
         <td>Baked Beans</td>
         <td>48 - 6 oz jars</td>
         <td>12.0000</td>
         <td>43</td>
      </tr>
      <tr>
         <td>7</td>
         <td>Buffelo Wings</td>
         <td>12 per box</td>
         <td>14.0000</td>
         <td>143</td>
      </tr>
   </tbody>
</table>

Note that now the table row that contains the column hreaders is contained inside a “THEAD” element and the actual data rows are contained inside a “TBODY” element.

In this way the plugin can ignore the header row and leave it in place as we filter the data rows.

You can downlaod the working sample [ HERE ]