Will "the Mighty" Strohl

Using jQuery to Search an HTML Table

Whenever I provide a listing of records, I always provide a way to search those records.  Typically, the chosen display for these records comes in the form of an HTML table.  My search usually consists of a little T-SQL magic in a stored procedure, which would require a PostBack of some kind to the web server to retrieve the filtered records.  This has never been efficient.  While thinking of this concept earlier today, I had an epiphany.  Why not use jQuery?

I could use jQuery to search the records in one of two ways.  First, the obvious way is to use the built-in AJAX feature in jQuery to perform the search on the database, and then return just the data needed to change the HTML mark-up on the fly.  While that is cool, my current listing of records is not that large.  I wanted a faster solution.

I chose to instead use jQuery to filter out the rendered HTML table rows that do not match the given query.  Since jQuery has an extensive and flexible selection of selectors, this was going to be both fun and easy!

I remembered that the jQuery library has a :contains(text) selector that allows you to select HTML tags by checking to see if the tag contains the given text.  For instance, the example below uses the contains selector to highlight the bullet with DotNetNuke® in the bullet.

  • ASP.Net
  • JavaScript
  • T-SQL
  • VB.Net
  • DotNetNuke

The jQuery code necessary to make that happen is a single line of code:

jQuery('li:contains(\'DotNetNuke\')').css('color', '#0000ff').css('font-weight', 'bold');

Pretty cool, huh?  Kind of.  Unfortunately, I found out the hard way that the :contains(text) selector is case-sensitive.  Thinking about the previous example, this means that using “dotnetnuke” in the contains selector would not have worked.  Fortunately, I found a great blog post by Rick Strahl that gave a case-insensitive version of the selector.  It worked like a charm!  (And also showed me the fact that my idea wasn’t new. D’oh!)

An Example

Expanding on this standard functionality, I created an HTML table with a listing of data records, and a search box above it to filter the results.  When you enter text into the textbox, the table automatically filters out rows that do not contain the text you’re searching for.  Here’s an example:

Search:   Cancel Search

First Last Address City State
John Dough 123 Main Street Orlando Florida
Jane Dough 4367 South Washington Avenue Bartow California
Bart Thompson 531 Townsend Circle Atlanta Georgia
Sherry Simpson 3346 Presario Lane, Apt. 123 Seattle Washington
Matt Damon 300 Pounds Street Boston Massachusetts

I don’t know about you, but I think that’s AWESOME!  Some features that I had put into this initially was a dynamic cancel search image, cancel search by pressing ESC, and displaying a message when the search has no results.

The Code Review

It was actually quite easy.  Here is a breakdown of the code used to make this happen.  (Make sure you look in the code comments to have an explanation of what I am doing and why.)

First, I created a function to reset the search, that could be used in a number of places as needed.

function resetSearch() {
    // clear the textbox
    jQuery('#txtSearch').val('');
    // show all table rows
    jQuery('#tblSearch tr').show();
    // remove any no records rows
    jQuery('.norecords').remove();
    // remove the cancel search image
    jQuery('#imgSearch').hide();
    // make sure we re-focus on the textbox for usability
    jQuery('#txtSearch').focus();
}

Next, I wrapped all of my code in a .ready() block to make sure it executes when it is supposed to, beginning with the initial steps to set-up the UI.

// hide the cancel search image
jQuery('#imgSearch').hide();
 
// reset the search when the cancel image is clicked
jQuery('#imgSearch').click(function() {
    resetSearch();
});
 
// cancel the search if the user presses the ESC key
jQuery('#txtSearch').keyup(function(event) {
    if (event.keyCode == 27) {
        resetSearch();
    }
});

Now that I had everything set-up, I was able to write the real magic of the search.  This is where the search gets executed and handled.  This is also in the.ready() block.

// execute the search
jQuery('#txtSearch').keyup(function() {
    // only search when there are 3 or more characters in the textbox
    if (jQuery('#txtSearch').val().length > 2) {
        // hide all rows
        jQuery('#tblSearch tr').hide();
        // show the header row
        jQuery('#tblSearch tr:first').show();
        // show the matching rows (using the containsNoCase from Rick Strahl)
        jQuery('#tblSearch tr td:containsNoCase(\'' + jQuery('#txtSearch').val() + '\')').parent().show();
        // show the cancel search image
        jQuery('#imgSearch').show();
    }
    else if (jQuery('#txtSearch').val().length == 0) {
        // if the user removed all of the text, reset the search
        resetSearch();
    }
 
    // if there were no matching rows, tell the user
    if (jQuery('#tblSearch tr:visible').length == 1) {
        // remove the norecords row if it already exists
        jQuery('.norecords').remove();
        // add the norecords row
        jQuery('#tblSearch').append('<tr class="norecords"><td colspan="5" class="Normal">No records were found</td></tr>');
    }
});

The Full Code Snippet

Here is the full snippet of code that you can use to try this yourself.

<html>
<head>
    <title>Untitled</title>
    <script language="javascript" type="text/javascript" src="/Resources/Shared/Scripts/jquery/jquery.min.js"></script>
   1:  
   2: </head>
   3: <body>
   4:     <p style="text-align:right;width:500px;">
   5:         <span style="font-weight:bold;">Search:</span> <input type="text" id="txtSearch" name="txtSearch" maxlength="50" />&nbsp; 
   6:         <img id="imgSearch" src="/images/cancel.gif" alt="Cancel Search" title="Cancel Search" style="width:150px;width:14px;height:14px;" />
   7:     </p>
   8:     <table id="tblSearch" cellpadding="2" cellspacing="0" border="1" style="width:500px;">
   9:         <tr style="font-weight:bold;">
  10:             <td>First</td>
  11:             <td>Last</td>
  12:             <td>Address</td>
  13:             <td>City</td>
  14:             <td>State</td>
  15:         </tr>
  16:         <tr>
  17:             <td>John</td>
  18:             <td>Dough</td>
  19:             <td>123 Main Street</td>
  20:             <td>Orlando</td>
  21:             <td>Florida</td>
  22:         </tr>
  23:         <tr>
  24:             <td>Jane</td>
  25:             <td>Dough</td>
  26:             <td>4367 South Washington Avenue</td>
  27:             <td>Bartow</td>
  28:             <td>California</td>
  29:         </tr>
  30:         <tr>
  31:             <td>Bart</td>
  32:             <td>Thompson</td>
  33:             <td>531 Townsend Circle</td>
  34:             <td>Atlanta</td>
  35:             <td>Georgia</td>
  36:         </tr>
  37:         <tr>
  38:             <td>Sherry</td>
  39:             <td>Simpson</td>
  40:             <td>3346 Presario Lane, Apt. 123</td>
  41:             <td>Seattle</td>
  42:             <td>Washington</td>
  43:         </tr>
  44:         <tr>
  45:             <td>Matt</td>
  46:             <td>Damon</td>
  47:             <td>300 Pounds Street</td>
  48:             <td>Boston</td>
  49:             <td>Massachusetts</td>
  50:         </tr>
  51:     </table>
  52:     <script language="javascript" type="text/javascript">
  53:         jQuery.expr[":"].containsNoCase = function(el, i, m) {
  54:             var search = m[3];
  55:             if (!search) return false;
  56:             return eval("/" + search + "/i").test($(el).text());
  57:         };
  58:  
  59:         jQuery(document).ready(function() {
  60:             // used for the first example in the blog post
  61:             jQuery('li:contains(\'DotNetNuke\')').css('color', '#0000ff').css('font-weight', 'bold');
  62:  
  63:             // hide the cancel search image
  64:             jQuery('#imgSearch').hide();
  65:  
  66:             // reset the search when the cancel image is clicked
  67:             jQuery('#imgSearch').click(function() {
  68:                 resetSearch();
  69:             });
  70:  
  71:             // cancel the search if the user presses the ESC key
  72:             jQuery('#txtSearch').keyup(function(event) {
  73:                 if (event.keyCode == 27) {
  74:                     resetSearch();
  75:                 }
  76:             });
  77:  
  78:             // execute the search
  79:             jQuery('#txtSearch').keyup(function() {
  80:                 // only search when there are 3 or more characters in the textbox
  81:                 if (jQuery('#txtSearch').val().length > 2) {
  82:                     // hide all rows
  83:                     jQuery('#tblSearch tr').hide();
  84:                     // show the header row
  85:                     jQuery('#tblSearch tr:first').show();
  86:                     // show the matching rows (using the containsNoCase from Rick Strahl)
  87:                     jQuery('#tblSearch tr td:containsNoCase(\'' + jQuery('#txtSearch').val() + '\')').parent().show();
  88:                     // show the cancel search image
  89:                     jQuery('#imgSearch').show();
  90:                 }
  91:                 else if (jQuery('#txtSearch').val().length == 0) {
  92:                     // if the user removed all of the text, reset the search
  93:                     resetSearch();
  94:                 }
  95:  
  96:                 // if there were no matching rows, tell the user
  97:                 if (jQuery('#tblSearch tr:visible').length == 1) {
  98:                     // remove the norecords row if it already exists
  99:                     jQuery('.norecords').remove();
 100:                     // add the norecords row
 101:                     jQuery('#tblSearch').append('<tr class="norecords"><td colspan="5" class="Normal">No records were found</td></tr>');
 102:                 }
 103:             });
 104:         });
 105:  
 106:         function resetSearch() {
 107:             // clear the textbox
 108:             jQuery('#txtSearch').val('');
 109:             // show all table rows
 110:             jQuery('#tblSearch tr').show();
 111:             // remove any no records rows
 112:             jQuery('.norecords').remove();
 113:             // remove the cancel search image
 114:             jQuery('#imgSearch').hide();
 115:             // make sure we re-focus on the textbox for usability
 116:             jQuery('#txtSearch').focus();
 117:         }
 118:     
</script>
</body>
</html>

Technorati Tags: ,


blog comments powered by Disqus