1

Desired Behaviour

Show or hide tables (whole tables, not table cells) based on search matches in the first two columns of all tables - if there is a match in the first or second <td> of a <tr>, show the table, else hide the table.

What I've Tried

jsFiddle

$("input").on("keyup", function() {
  var matcher = new RegExp($(this).val(), "gi");
  $(".my_table")
    .hide()
    .filter(function() {
      return matcher.test($(this).find("td").text());
    })
    .show();
});
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 50%;
}

td,
th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<input type="text">

<table class="my_table">
  <thead>
    <tr>
      <th>
        First Name
      </th>
      <th>Last Name</th>
      <th>Text</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>David</td>
      <td>Johnman</td>
      <td>Here is some text</td>
    </tr>
    <tr>
      <td>Felix</td>
      <td>Mann</td>
      <td>Cake</td>
    </tr>
    <tr>
      <td>Peter</td>
      <td>Pan</td>
      <td>Green green grass of home</td>
    </tr>
</table>

<br><br>

<table class="my_table">
  <thead>
    <tr>
      <th>
        First Name
      </th>
      <th>Last Name</th>
      <th>Text</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>David</td>
      <td>Stone</td>
      <td>Here is text</td>
    </tr>
    <tr>
      <td>Trevor</td>
      <td>Barry</td>
      <td>Cake</td>
    </tr>
    <tr>
      <td>Xylophone</td>
      <td>Greet</td>
      <td>Green green grass of home</td>
    </tr>
</table>

Not averse to putting classes on the first two table cells in each row if that helps target the search, just not entirely sure how to target the instance of the table that is being filtered.

Eventually I am wanting to start the filtering after typing 3 characters, so will probably wrap the code in a conditional check on input length, and show all tables when input length is 0 (after backspacing content).

user1063287
  • 10,265
  • 25
  • 122
  • 218

2 Answers2

3

Just posting a solution I ended up developing even though it was different from accepted answer (as it moved in a different direction) in case it helps others with another approach. It is not lean or optimised but this may help others see different "parts" of the logic.

It filters tables based on search input (minimum 3 characters) and highlights matched text. It only matches content in the first two <td>'s of each <tr> - in the first cell it matches the beginning of the content, in the second cell it matches anywhere in the content.

function search_tds(input_val) {
  $("table").each(function() {
    var match_counter = 0;

    // instance of table
    var $this = $(this);
    // first and second cells in each row
    var $tds = $this.find("td:nth-child(1),td:nth-child(2)");

    $tds.each(function(ind, val) {
      var cell = $tds[ind];
      var cell_text = cell.innerText;

      // if the first cell, perform check at start of string
      if ($(this).is(":nth-child(1)")) {
        var is_at_start_of_string =
          cell_text.toLowerCase().indexOf(input_val.toLowerCase()) === 0;
        // remove span tags to remove 'history' state
        // ie if there was existing match and then pasted in
        // another matching value
        if (is_at_start_of_string === false) {
          cell.innerHTML = cell.innerText;
        }
      } else if ($(this).is(":nth-child(2)")) {
        // if the second cell, perform check anywhere in string
        var exists_in_string =
          cell_text.toLowerCase().indexOf(input_val.toLowerCase()) !== -1;
        // remove span tags to remove 'history' state
        // ie if there was existing match and then pasted in
        // another matching value
        if (exists_in_string === false) {
          cell.innerHTML = cell.innerText;
        }
      }

      if (is_at_start_of_string === true || exists_in_string === true) {
        match_counter += 1;
        // cell.innerHTML = cell.innerText.replace(
        //  input_val,
        //  '<span class="matched_text">' + input_val + "</span>"
        //);
        // to replace with accurate case, see:  
        // https://stackoverflow.com/a/3294644/1063287
        var reg = new RegExp(input_val, 'i');
        cell.innerHTML = cell.innerText.replace(reg, '<span class="matched_text">$&</span>');
      }
    });

    if (match_counter > 0) {
      $(this).css("display", "table");
    } else {
      $(this).css("display", "none");
    }
  });
}

$(document).on("keyup", "input", function() {
  var input_val = $(this).val();
  var input_length = input_val.length;
  if (input_length > 2) {
    search_tds(input_val);
  } else if (input_length <= 2) {
    $("table").css("display", "table");
    $("span").removeClass("matched_text");
  }
});
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 80%;
  table-layout: fixed;
  margin-left: auto;
  margin-right: auto;
}

td,
th {
  border: 1px solid #000;
  text-align: left;
  padding: 8px;
  width: 33.3%;
}

.matched_text {
  background: yellow;
}

input {
  padding: 10px;
  width: 80%;
  margin: 10px auto;
  display: block;
  background: #eee;
  color: #505050;
  border: 1px solid #b0b0b0;
  font-size: 20px;
}

* {
  box-sizing: border-box;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" placeholder="enter search text (3 characters minimum)...">

<table>

  <tbody>
    <tr>
      <td>011010</td>
      <td>best</td>
      <td>this text is not searched zest</td>
    </tr>
    <tr>
      <td>020110</td>
      <td>vest 011010</td>
      <td>this text is not searched jest</td>
    </tr>
  </tbody>

</table>

<br>

<table>

  <tbody>
    <tr>
      <td>808080</td>
      <td>Jest</td>
      <td>this text is not searched best</td>
    </tr>
    <tr>
      <td>805601</td>
      <td>Pest</td>
      <td>this text is not searched chest</td>
    </tr>
  </tbody>

</table>

<br>

<table>

  <tbody>
    <tr>
      <td>020101</td>
      <td>zest</td>
      <td>this text is not searched vest</td>
    </tr>
    <tr>
      <td>501025</td>
      <td>chesT</td>
      <td>this text is not searched 808080</td>
    </tr>
  </tbody>

</table>
user1063287
  • 10,265
  • 25
  • 122
  • 218
1

You can limit the search to the :first-child and second child (:nth-child(2)) or-ed together:

return matcher.test($(this).find("td:first-child,td:nth-child(2)").text());

Alternatively, as you have suggested, you can add a class to the first two td, e.g. searchable, and refine the query string to td.searchable.

sauerburger
  • 4,569
  • 4
  • 31
  • 42
  • I tried this code and when typing `da`, for example, the second table is hidden and the first table is visible, even though both tables contain the searched for text - so there is something in my original logic that is missing. [jsFIddle link](https://jsfiddle.net/rwone/echd66uc) – user1063287 Aug 25 '17 at 03:58
  • 1
    There seems to be an [issue](https://stackoverflow.com/questions/1520800/why-does-a-regexp-with-global-flag-give-wrong-results) with the `RegExp` object when using the `g` option, see this [example](https://jsfiddle.net/c5mjukxf/1/) . When I drop the `g` in your fiddle, it works. – sauerburger Aug 25 '17 at 08:58