Search code examples
jqueryjquery-uisearchfiltercollection

filter table rows with multiple filters


I have a table with several rows and columns and a set of search fields. I would like to be able to show / hide the rows that match / don't match the search fields. Each field is related to a column of the table. I have been partly successful in this task, because the filtering is done correctly (as you can see here). However, I would like to fix two things.

  • First is the fact that the jquery script also hides the table head.
  • Secondly, I would like to be able to filter the rows as I type. Ex. if I type only 'J' at name box, everything disappears, because there is no row with the name 'J'. However, we have got 'James' and 'Jamie', which are potential match. And I would like to keep them until the name is fully typed. I tried to do it with s1.localeCompare(s2) (link here), but it does not work.

By the way, no need to worry about uppercase / lowercase typing. I actually take care of it in the original code, but tried to keep it simple here.

The code here:

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
        <script>
        $(document).ready(function(){
            table = $("#MI6"); //set table name
            search_field = new Object();
            ///we create it as an object, initially empty
            $('.search-key').on('change keyup paste', function () {
                search_field['name']      = $( "#name" ).val();
                search_field['lastname']  = $("#lastname").val();
                search_field['number']    = $("#number").val();

                table.find('tr').each(function () {
                    current_row = $(this); //keep track of the row being checked, iterate through it's cells
                    var display = 0;
                    current_row.show();
                    $(this).find('td').each(function() {
                    //when we stumble upon the data used as a search criteria
                        cell_value = $(this).html(); //gets the value of the cell being checked
                        if (cell_value == search_field[this.id] || search_field[this.id] == '') {
                            display++;    
                        }
                    });
                    if (display < 3) {
                        current_row.hide(); //if this cell is a match, or we no longer want to use it as a search criteria, the row is displayed
                    }
                });

            });   
        });
        </script>
    </head>
    <body>
        <input type="text" id="name" class="search-key" placeholder="name">
        <input type="text" id="lastname" class="search-key" placeholder="lastname">
        <input type="number" id="number" class="search-key" placeholder="number">
        <p></p>
        <table id="MI6">
            <tr>
                <th>Firstname</th>
                <th>Lastname</th> 
                <th>Number</th>
            </tr>
            <tr>
                <td id="name">James</td>
                <td id="lastname">Bond</td> 
                <td id="number">7</td>
            </tr>
            <tr>
                <td id="name">Vesper</td>
                <td id="lastname">Lynd</td> 
                <td id="number">6</td>
            </tr>
            <tr>
                <td id="name">Rene</td>
                <td id="lastname">Mathis</td> 
                <td id="number">5</td>
            </tr>
    </table>
    </body>
</html>

Solution

  • To answer your first question, simply omit the first row of the table from the collection using .not(':first'):

    table.find('tr').not(':first')
    

    In order to do partial string matching, you can use indexOf().

    The indexOf() method returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found.

    I noticed that you can duplicate ids in your markup, they must be unique.

    Your script could be rewritten to be more dynamic with a couple of small changes to the markup:

    <td data-input="name">Rene</td>
    <td data-input="lastname">Mathis</td> 
    <td data-input="number">5</td>
    

    You can then use the data-input to target the corresponding input. You can combine this with jQuery's filter() method to return matching rows:

    /* $rows = table.find('tr').not(':first') */
    $rows.hide().filter(function() {
    
      return $(this).find('td').filter(function() {
    
        var tdText = $(this).text().toLowerCase(),
            inputValue = $('#' + $(this).data('input')).val().toLowerCase();
    
        return tdText.indexOf(inputValue) != -1;
    
      }).length == $(this).find('td').length;
    
    }).show();
    

    The above first hides each row, and then filters. Inside of there, each contained td is filtered, comparing its text against the value of the corresponding input. If a match is found, the td is returned. It then checks the number of matching td elements against the number of td elements in that row, if they are the same, all fields contain a partial match, and the entire row is returned. Finally, any matching rows are then shown.

    This way will allow you to add more inputs, and tds without having to modify the code. You'd just have to set the id on the input, and add the corresponding data-input to the td elements.

    Here's a complete example