GrumpyCrouton
GrumpyCrouton

Reputation: 8621

Using javascript table search on 2 similar tables

I have 2 tables with very similar structure, but different data within.

I have this javascript that I found that allows me to search the table easily given a search box. I am very much a javascript newbie, it is not my strong point in code.

$(document).ready(function() {
    $(".search").keyup(function() {
        var searchTerm = $(".search").val();
        var listItem = $('.results tbody').children('tr');
        var searchSplit = searchTerm.replace(/ /g, "'):containsi('")

        $.extend($.expr[':'], {
            'containsi': function(elem, i, match, array) {
                return (elem.textContent || elem.innerText || '').toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
            }
        });

        $(".results tbody tr").not(":containsi('" + searchSplit + "')").each(function(e) {
            $(this).attr('visible', 'false');
        });

        $(".results tbody tr:containsi('" + searchSplit + "')").each(function(e) {
            $(this).attr('visible', 'true');
        });

        var jobCount = $('.results tbody tr[visible="true"]').length;
        $('.counter').text(jobCount + ' item');

        if (jobCount == '0') {
            $('.no-result').show();
        } else {
            $('.no-result').hide();
        }
    });
});

This code works beautifully if there is only 1 table on the page. My problem is, I have 2.

Here is one of the tables, the other one is exactly the same except for the headers and the actual data contents.

<div class="form-group pull-right">
        <div class="input-group">
            <input type="text" class="search form-control" placeholder="Search:">
            <span class="input-group-btn">
                <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#devices">
                    Add Device
                </button>
            </span>
        </div>
    </div>
    <span class="counter pull-right"></span>
    <table class="table table-responsive table-hover table-bordered results">
        <thead>
            <tr>
                <th class="col-md-3">Device #</th>
                <th class="col-md-3">Brand</th>
                <th class="col-md-3">Model</th>
                <th class="col-md-3">Color</th>
            </tr>
            <tr class="warning no-result">
                <td colspan="7"><i class="fa fa-warning"></i> No result</td>
            </tr>
        </thead>
        <tbody>

            <?

            $sql = "SELECT * FROM devices";

            if(!$result = $db->query($sql)){
                die('There was an error running the query [' . $db->error . ']');
            }

            while($row = $result->fetch_assoc()){                                   
                ?>
                <tr>
                    <th scope="row"><?=$row['uniqueID'];?></th>
                    <td><?=$row['Brand'];?></td>
                    <td><?=$row['Model'];?></td>
                    <td><?=$row['Color'];?></td>
                </tr>
                <?
            }
            ?>


        </tbody>
    </table>

I have tried things like changing the selectors in javascript, but I can't get it to work. I just end up breaking it and reverting back to the original code. I have no idea how to do this.

How can I alter the javascript code to handle the 2 tables independently?

JSFiddle: https://jsfiddle.net/vdemqwLx/

Upvotes: 0

Views: 134

Answers (2)

Gabriel
Gabriel

Reputation: 2190

The problem here is that with $(".results tbody") and all the other selectors you are selecting both tables. You simply have to select the proper table based in which input field produced the event.

To make it less dependant of the HTML structure, I'd suggest you using data attributes to identify the corresponding table.

<input type="text" class="search form-control" placeholder="Search:" data-table="table1" data-counter="counter1">
...
<span id="counter1" class="counter pull-right"></span>
<table id="table1" class="table table-responsive table-hover table-bordered results">
...

(the same with the other)

Then in the JS, instead of searching in the whole document, search within these

//this should go outside
$.extend($.expr[':'], {
    'containsi': function(elem, i, match, array) {
        return (elem.textContent || elem.innerText || '').toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
    }
});

$(document).ready(function() {
    $(".search").on("input",function() { //input will also detect paste, undo, etc.
        //this executes for both inputs, lets get the table and counter IDs
        var tableId=$(this).data("table");
        var tableElem=$("#"+tableId); //select the table by its ID
        var counterId=$(this).data("counter");

        var searchTerm = $(this).val(); //use this, otherwise you're always getting the value of the first .search
        var listItem = $(tableElem).find("tbody").children('tr'); //now find within the table (otherwise always selects the first .resulst)
        var searchSplit = searchTerm.replace(/ /g, "'):containsi('")

        $(tableElem).find("tbody tr").not(":containsi('" + searchSplit + "')").each(function(e) {
            $(this).attr('visible', 'false');
        });

        $(tableElem).find("tbody tr:containsi('" + searchSplit + "')").each(function(e) {
            $(this).attr('visible', 'true');
        });

        var jobCount = $(tableElem).find("tbody tr[visible='true']").length;
        $("#"+counterId).text(jobCount + ' item'); //the same with the counter

        if (jobCount == '0') {
            $(tableElem).find(".no-result").show();
        } else {
            $(tableElem).find(".no-result").hide();
        }
    });
});

Test it, I wrote directly in here. Ask me if you need more explaination on something.

Upvotes: 1

charlietfl
charlietfl

Reputation: 171679

This is a typical pattern for repeating components within a page

Isolate instances by traversing up to a main container and looking inside that container instance for classes of elements using closest() and find()

Or start with the main container and use an each along with find() to isolate it's internal elements

First approach

$(".search").keyup(function(){
   // get group instance
   var $fGroup = $(this).closest('.form-group'),
       //isolate rows within group
       $rows = $fGroup.find(".results tbody tr") 
       //visible rows filter
       var $visibleRows = $rows.filter(":containsi('" + searchSplit + "')").attr('visible','true');

       $rows.not($visibleRows).attr('visible','false');
      // no-result instance
      $fgroup.find( ('.no-result').toggle($visibleRows.length);

});

Second approach

$('.form-group').each(function() {
  var $fGroup = $(this);
  // apply event listener to instance of search
  $fGroup.find(".search").keyup(function() {

    var $rows = $fGroup.find(".results tbody tr")
      //visible rows filter
    var $visibleRows = $rows.filter(":containsi('" + searchSplit + "')").attr('visible', 'true');

    $rows.not($visibleRows).attr('visible', 'false');
    // no-result instance
    $fgroup.find('.no-result').toggle($visibleRows.length);


  });
})

Upvotes: 0

Related Questions