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:
substring1
- substring search (from 1st char) (default)
e.g. man will match "manhood" and "man is evil" NOT "superman" and "he is a man"
substring
- substring search (anywhere within)
e.g. man will match "manhood" and "superman" and "he is a man"
full
- full string search
e.g. man will match "man" NOT "manhood", "superman", "man is evil" and "he is a man"
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