Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Table Filtering/Searching using DHTML and JavaScript

0.00/5 (No votes)
21 Jun 2007 1  
Easily add table filtering/searching capability to your webpage without requiring a round trip to the server.

Latest version is v1.0e

Background

A while ago, I was working on a typical web application that allows a user to browse and manage customer data stored in a database, among other things. The normal thing to do would be implement such logic on the server-side. However, a specific requirement was that the user must be able to quickly search and find one or more records from a table using one or more search criteria. To make a round trip to the server and back is considered too expensive. Therefore a client-side solution is required.

This solution uses a combination of JavaScript and DHTML. The script was created with flexibility, reusability and simplicity in mind. I.e. The developer (meaning you) is only required to include the script, setup a few parameters, and make the JavaScript calls in the appropriate event handlers.

Features

This script offers the following set of features:

  • search criteria can be a text box, single or multi-select list box
  • allows a combination of search criteria (as an AND operation)
  • 4 types of matching strategies are available:
    1. substring1 - substring search (from 1st char) (default)
      e.g. man will match "manhood" and "man is evil" NOT "superman" and "he is a man"
    2. substring - substring search (anywhere within)
      e.g. man will match "manhood" and "superman" and "he is a man"
    3. full - full string search
      e.g. man will match "man" NOT "manhood", "superman", "man is evil" and "he is a man"
    4. item - search for a word/phrase in a comma separated string
      e.g. man will match "man,woman,child" NOT "superman,superwoman,kid" and "boy,girl,dog"
  • performs full string match (in substring1 mode) if last char in search string is a white space.
  • allows search to be turned off/on.
  • *NEW* allows filtering by checkbox status in the table. Search criteria can be expressed as either a checkbox or a single select dropdown list.

Usage

In order to use this script, you will first have to include it in your HTML as follows:

<SCRIPT LANGUAGE="JavaScript" SRC="tablefilter.js" TYPE='text/javascript'>
</SCRIPT>

And the recommended place to include this code is in the head section of the HTML.

Setting up the table

First, give the table element a handle using the attribute id or name. This is required so that the script can access the rows within the table using this reference. In the cell elements, include a custom attribute called TF_colKey and give it a label to identify the column. Note that this attribute is only required in those columns that will be searched on. Make sure that all cells in a particular column use the same label. Also note that the label names are "case-insensitive".

An example of how this will look in HTML is shown here:

<table id="dataTable">
  <tbody> 
    <tr> 
      <td TF_colKey="ckbox"><INPUT type="checkbox"></td> 
      <td TF_colKey="name">Joe</td>
      <td TF_colKey="group">Alpha</td>
      <td TF_colKey="salary">400</td>
      <td TF_colKey="zone">North,South</td>
      <td TF_colKey="status">Off-Site</td>
    </tr>
    <tr> 
      <td TF_colKey="name">Celest</td>
      <td TF_colKey="group">Beta</td>
      <td TF_colKey="salary">5000</td>
      <td TF_colKey="zone">North</td>
      <td TF_colKey="status">OK</td>
    </tr>
  </tbody>
</table>

Now that's the easy part done!

Setting up the search form

The search criteria input mechanism is implemented using an HTML form. In order to identify the group of search inputs, it is necessary to give the form element a handle using either the id or name attribute.

<form name="filter" onsubmit="TF_filterTable(dataTable, filter);return false" 
         onreset="_TF_showAll(dataTable)">

Override onsubmit to call the main script function TF_filterTable(table, form) passing in the handles to the table and form elements. By doing so, the search will be activated when the user hits the Enter key. Since this form does not actually submit to a server, append a return false to cancel the submission.

You may also want to allow the user to easily reset the search form and show all the rows by having onreset call the script function _TF_showAll(table) passing in the handle to the table element.

Using a text input box

Use the following code to implement a text input search field:

<input type="text" TF_colKey="name" TF_searchType="full" 
          onkeyup="TF_filterTable(dataTable, filter)">

The attribute TF_colKey should reflect the name of the column where this search parameter is applicable. So in the case above, the entry in the textbox will be matched against any cells that has the value "name" in its (the cell's) TF_colKey attribute. Again, this attribute is only necessary for those input fields that will be used in the search process. This is especially useful for building complex query inputs (explained in a later section).

The attribute TF_searchType is optional and it defines the type of matching that will be performed. The available choices are substring1 - substring search from first character (default), substring - substring search anywhere within, full - only matches the full string and item - matches any comma-seperated word/phase. In the example above, we are using full string search.

To activate the search as soon as the user presses a key, override onkeyup to call the main search function TF_filterTable(table, form) passing in the handles to the table and form elements.

Using a selection list input box

Use the following code to implement a selection list input search field:

<select TF_colKey="status" onChange="TF_filterTable(dataTable, filter)">
  <option TF_not_used value="">-</option>
  <option value="OK">OK</option>
  <option value="Off-Site">Off-Site</option>
  <option value="On Leave">On Leave</option>
</select>

Again, the attribute TF_colKey should reflect the name of the column where this search parameter is applicable. So in the case above, the entry in the listbox will be matched against any cells that have the value "status" in its (the cell's) TF_colKey attribute.

As with all other search inputs, the optional attribute TF_searchType can be specified in the select element to define an alternative matching strategy.

Note that the value attribute of the option element will be used as the search string, NOT the enclosing text. If a particular option is not meant to be searched on (e.g. a default value in a single select drop down listbox like the first option in the example above), use the custom attribute TF_not_used to exclude it from the search.

To activate the search as soon as a selection is made, override onChange to call the main search function TF_filterTable(table, form) passing in the handles to the table and form elements.

If a multiple selection list is used (by specifying the size attribute in the select element), then the selections are matched in an OR manner. Check out the demo to find out how this works.

Filtering the content of the table based on checkbox state

By popular demand, I've added a new feature that allows the filtering of rows based on the state of checkbox. To use this feature, you can specify the search box in one of two ways; either a checkbox or a dropdown list. In the above demo, I've chosen to use the dropdown list for ease of implementation.

Do note that as three states are required to express the search criteria (i.e. checked, unchecked and off), plus the fact that a HTML checkbox cannot be used in tri-state mode, you will need to implement another control (e.g. checkbox) to enable or disable the search checkbox to fully express the search criteria, which IMHO, looks rather clumsy. In any case, the code fully supports this use case and I've done some preliminary testing so it should work as advertised.

The code to display the dropdown list search criteria is as follows:

<SELECT onchange="TF_filterTable(dataTable, filter)" 
                TF_colKey="ckbox" TF_searchType="checkbox">
    <OPTION value="" selected TF_not_used>-</OPTION> 
    <OPTION value="true">checked</OPTION> 
    <OPTION value="false">unchecked</OPTION>
</SELECT>        

Again, the attribute TF_colKey should reflect the name of the column where this search parameter is applicable. So in the case above, the entry in the listbox will be matched against any cells that have the value "ckbox" in its (the cell's) TF_colKey attribute.

Note that the use of TF_searchType="checkbox" is 'mandatory' for this code to work. In addition, The option value for checked 'must' be a string with the value "true" and similarly, the option value for unchecked 'must' be a string with the value "false".

For those who are interested, the code to display the checkbox search criteria is as follows:

<INPUT type="checkbox" TF_colKey="ckbox" 
           onclick="TF_filterTable(dataTable, filter)">

Building complex query inputs

One of the challenges I faced in the project was to build a single search criterion using multiple input fields. E.g. table value is "100 USD" and the inputs are constructed using one text box for the numeric part, and one selection list for the units portion (See demo above for a live example).

At first thought, I had wanted to incorporate this feature directly in the script. But I've yet to find an elegant way to do it; the implementation wasn't robust enough to handle all the combinations plus using it becomes really complicated. Perhaps in a future version. In the meantime however, here's an approach you can use:

Define an intermediate input field that is hidden from view (Use either a hidden input field or set the display style to none). Write a custom function to perform the necessary formatting (e.g. concatenating the values) from the inputs that make up the complex query and place the result in the hidden field. Then in the event handler of the search inputs, make a call to the formatting function before calling the table filtering function. And that's it!

Here's the code snippet that does the complex query in the demo above:

<input type="text" name="salText" 
    onblur="TF_concat_and_set(salText, salSelect, salHidden);
    TF_filterTable(dataTable, filter);">
<select name="salSelect" 
    onChange="TF_concat_and_set(salText, salSelect, salHidden);
    TF_filterTable(dataTable, filter);">
 <option value="" TF_not_used>-</option>
 <option value=" USD">USD</option>
 <option value=" SGD">SGD</option>
 <option value=" YEN">YEN</option>
</select>
<input type="hidden" name="salHidden" TF_colKey="salary" 
                        TF_searchType="substring">

Notice that the custom attribute TF_colKey is not defined for both the text and select elements as we do not want them to be included in the search; we define that attribute in the hidden input field instead. The function TF_concat_and_set will perform a concatenation of the two input values and place the result in the hidden input. This utility function is included in the table filter script as a convenience for other users.

Turning the search form on/off

A utility function TF_enableFilter(table, form, checkbox) is provided to easily turn the search feature on or off. In the off state, the table will show all the rows and the search form will be hidden. To enable this feature, use the following code snippet:

<input type="checkbox" checked 
       onclick="TF_enableFilter(dataTable, filter, this)">

Demo code

<script type="text/javascript" src="tablefilter/tablefilter.js" language="JavaScript"></script>
<table>
    <tr valign="top" align="left">
        <td>
        <table cellspacing="0" cellpadding="0" border="1">
            <tr valign="middle">
                <td colspan="6">
                <div align="center">
                    <strong>Sales Employee Table </strong></div>
                </td>
            </tr>
            <tr>
                <td>
                <div style="width: 585px;">
                    <table cellspacing="0" cellpadding="2" border="1" id="header">
                        <tr>
                            <th width="20">
                            <input type="checkbox" 
                                onclick="TF_check_uncheck_all_rows(dataTable,this.checked,0,0);" />
                            </th>
                            <th width="80">Name</th>
                            <th width="100">Group</th>
                            <th width="100">Salary</th>
                            <th width="180">Zone</th>
                            <th width="95">Status</th>
                        </tr>
                    </table>
                </div>
                <div style="border: 1px solid gray; padding: 0px; margin: 0px; overflow: auto; width: 585px;height: 132px;">
                    <table cellspacing="0" cellpadding="2" border="1" id="dataTable">
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Joe</td>
                            <td width="100" valign="top" tf_colkey="group">Alpha</td>
                            <td width="100" valign="top" tf_colkey="salary">400 
                            USD</td>
                            <td width="180" valign="top" tf_colkey="zone">
                            North,South</td>
                            <td width="80" valign="top" tf_colkey="status">
                            Off-Site</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Celest</td>
                            <td width="100" valign="top" tf_colkey="group">Beta</td>
                            <td width="100" valign="top" tf_colkey="salary">
                            50000 YEN</td>
                            <td width="180" valign="top" tf_colkey="zone">North</td>
                            <td width="80" valign="top" tf_colkey="status">OK</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Brian</td>
                            <td width="100" valign="top" tf_colkey="group">Alpha</td>
                            <td width="100" valign="top" tf_colkey="salary">4000 
                            SGD</td>
                            <td width="180" valign="top" tf_colkey="zone">
                            North,East,South</td>
                            <td width="80" valign="top" tf_colkey="status">
                            Off-Site</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">David</td>
                            <td width="100" valign="top" tf_colkey="group">Alpha</td>
                            <td width="100" valign="top" tf_colkey="salary">7000 
                            SGD</td>
                            <td width="180" valign="top" tf_colkey="zone">
                            West,South</td>
                            <td width="80" valign="top" tf_colkey="status">On 
                            Leave</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Pauline</td>
                            <td width="100" valign="top" tf_colkey="group">Gamma</td>
                            <td width="100" valign="top" tf_colkey="salary">450 
                            USD</td>
                            <td width="180" valign="top" tf_colkey="zone">West</td>
                            <td width="80" valign="top" tf_colkey="status">On 
                            Leave</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">April</td>
                            <td width="100" valign="top" tf_colkey="group">Gamma</td>
                            <td width="100" valign="top" tf_colkey="salary">2400 
                            SGD</td>
                            <td width="180" valign="top" tf_colkey="zone">North, 
                            West</td>
                            <td width="80" valign="top" tf_colkey="status">OK</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Sharon</td>
                            <td width="100" valign="top" tf_colkey="group">Beta</td>
                            <td width="100" valign="top" tf_colkey="salary">2000 
                            SGD</td>
                            <td width="180" valign="top" tf_colkey="zone">
                            East,South</td>
                            <td width="80" valign="top" tf_colkey="status">
                            Off-Site</td>
                        </tr>
                        <tr id="dataRow">
                            <td width="20" valign="top" tf_colkey="ckbox">
                            <input type="checkbox" /> </td>
                            <td width="80" valign="top" tf_colkey="name">Paul</td>
                            <td width="100" valign="top" tf_colkey="group">Beta</td>
                            <td width="100" valign="top" tf_colkey="salary">2500 
                            SGD</td>
                            <td width="180" valign="top" tf_colkey="zone">
                            North,South,East,West</td>
                            <td width="80" valign="top" tf_colkey="status">On 
                            Leave</td>
                        </tr>
                    </table>
                </div>
                </td>
            </tr>
        </table>
        </td>
    </tr>
    <tr valign="top" align="center">
        <td height="209">
        <table width="601" height="40" cellspacing="0" cellpadding="0" border="0">
            <tr valign="bottom">
                <td height="40">
                <input type="checkbox" checked="true" onclick="TF_enableFilter(dataTable, filter, this)" /> 
                Enable Filter </td>
            </tr>
            <tr>
                <td>
                <form onsubmit="TF_filterTable(dataTable, filter);return false" 
                    onreset="_TF_showAll(dataTable)" name="filter">
                    
                    <table width="604" height="40" cellspacing="0" cellpadding="0" border="0">
                        <tr>
                            <td height="40">
                            <input type="button" value="Reset filters" onclick="filter.reset()" />
                            </td>
                        </tr>
                        <tr valign="bottom">
                            <td width="325" height="40">Name:(substring1 search)</td>
                            <td width="250" height="20">Group:(full string 
                            search, Multi-select)</td>
                            <td width="133" height="20">Status:(full string 
                            search)</td>
                        </tr>
                        <tr>
                            <td width="325" height="34">
                            <input tf_colkey="name" size="40" onkeyup="TF_filterTable(dataTable, filter)" />
                            </td>
                            
                            <td width="250" height="24">
                            <select tf_searchtype="full" tf_colkey="group" size="3" multiple="true" 
                                onchange="TF_filterTable(dataTable, filter)">
                                <option value="Alpha">Alpha</option>
                                <option value="Beta">Beta</option>
                                <option value="Gamma">Gamma</option>
                            </select> </td>
                            
                            <td width="133" height="34">
                            <select tf_searchtype="full" tf_colkey="status" 
                                onchange="TF_filterTable(dataTable, filter)">
                                <option>-</option>
                                <option value="OK">OK</option>
                                <option value="Off-Site">Off-Site</option>
                                <option value="On Leave">On Leave</option>
                            </select> </td>
                        </tr>
                        <tr valign="bottom">
                            <td width="325" height="40">Salary:(complex query)</td>
                            <td width="250" height="20">Zone:(Item search, 
                            Multi-select)</td>
                            <td width="250" height="20">Checkbox: (Filter by 
                            status of checkbox)</td>
                        </tr>
                        <tr>
                            <td width="325" height="34">
                            <input name="salText" size="30" 
                                onblur="TF_concat_and_set(salText, salSelect, salHidden);TF_filterTable(dataTable, filter);" />
                            <select name="salSelect" 
                                onchange="TF_concat_and_set(salText, salSelect, salHidden);TF_filterTable(dataTable, filter);">
                            <option selected="true">-</option>
                            <option value=" USD">USD</option>
                            <option value=" SGD">SGD</option>
                            <option value=" YEN">YEN</option>
                            </select>
                            <input type="hidden" tf_searchtype="substring" tf_colkey="salary" name="salHidden" />
                            </td>
                            <td width="250" height="34">
                            <select tf_searchtype="item" tf_colkey="zone" size="4" multiple="true" 
                                onchange="TF_filterTable(dataTable, filter)">
                            <option value="East">East</option>
                            <option value="West">West</option>
                            <option value="North">North</option>
                            <option value="South">South</option>
                            </select> </td>
                            <td>
                            <select tf_searchtype="checkbox" tf_colkey="ckbox" name="salSelect2" 
                                onchange="TF_filterTable(dataTable, filter)">
                            <option selected="true">-</option>
                            <option value="true">checked</option>
                            <option value="false">unchecked</option>
                            </select></td>
                        </tr>
                    </table>
                </form>
                </td>
            </tr>
            <tr>
                <td></td>
            </tr>
        </table>
        </td>
    </tr>
</table>

Browser Compatibility

This script and the demo were developed and tested on the IE platform only, since the project did not require Netscape compatibility. It shouldn't be too difficult to make it work on Netscape and I'll probably get round to it when I have the time. I would appreciate any help in this area as well.

Conclusion

Well, that's it folks. It has been good fun developing this script and writing this article. Hope you will enjoy it too! Oh, and I most certainly welcome any feedback, bug reports or suggestions.

Version History

  • 19/6/2007 - version 1.0e release

    • Added convenience function TF_check_uncheck_all_rows to help check/uncheck all rows in the table
    • Added feature to allow filtering by checkbox status via checkbox OR single select dropdown list
    • Added feature to allow filtering by checkbox status
  • 24/8/2001 - version 1.0d release

    • Added functions _TF_get_value and TF_concat_and_set
    • Hidden input can now be used as a search field
    • Implemented "substring" search mode
    • Improved robustness/flexibility: if a cell <td> in the table or input <input>/<select> in the search form does not take part in the search (hence does not have the attribute "TF_colKey"), the script will gracefully ignore it. Previously, it would generate a script error
    • Reintroduced "TF_not_used" custom attribute to the option element. I've apparently managed to loose it in the v1.0 pre-release *DUH!*
  • 16/8/2001 - version 1.0c release

    • Modified _TF_trimWhitespace to trim the front as well
    • Fixed bug in _TF_filterTable that cause AND search combinations not to work properly
  • 9/8/2001 - version 1.0b release

    • Added _TF_showAll function
    • Modified _TF_filterTable to use_TF_shouldShow function.
    • Added TF_searchType attribute to define a search type
    • Implemented "item" search
    • Added _TF_shouldShow function
  • 26/7/2001 - version 1.0a release

    • Added _TF_trimWhitespace function
    • Modified _TF_filterTable single condition search to include full pattern search if the last char of the search string is a whitespace
  • 14/6/2001 - version 1.0 initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here