29

I have a code to filter a table. It will filter only based on first column. How to make it filter second column alone. Also how to filter complete table?

I am not able to figure out the method to do it. I trying to get help to do it without any other external libraries.

<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.." title="Type in a name">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>

<script>
function myFunction() {
  var input, filter, table, tr, td, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("myTable");
  tr = table.getElementsByTagName("tr");
  for (i = 0; i < tr.length; i++) {
    td = tr[i].getElementsByTagName("td")[0];
    if (td) {
      if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
        tr[i].style.display = "";
      } else {
        tr[i].style.display = "none";
      }
    }       
  }
}
</script>

JS Fiddle

ponury-kostek
  • 7,824
  • 4
  • 23
  • 31
qwww
  • 1,313
  • 4
  • 22
  • 48

8 Answers8

31

Filter all Html Table:

const myFunction = () => {
  const trs = document.querySelectorAll('#myTable tr:not(.header)')
  const filter = document.querySelector('#myInput').value
  const regex = new RegExp(filter, 'i')
  const isFoundInTds = td => regex.test(td.innerHTML)
  const isFound = childrenArr => childrenArr.some(isFoundInTds)
  const setTrStyleDisplay = ({ style, children }) => {
    style.display = isFound([
      ...children // <-- All columns
    ]) ? '' : 'none' 
  }
  
  trs.forEach(setTrStyleDisplay)
}
input#myInput { width: 220px; }
table#myTable { width: 100%; }
table#myTable th { text-align: left; padding: 20px 0 10px; }
<input 
  type="text" 
  id="myInput" 
  onkeyup="myFunction()" 
  placeholder="Search for names or countries.." 
  title="Type in a name or a country">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>

Filter only specific Html Table Columns

  • Countries, in this case it is the column with index 1

const myFunction = () => {
  const columns = [
    { name: 'Name', index: 0, isFilter: false },
    { name: 'Country', index: 1, isFilter: true }
  ]
  const filterColumns = columns.filter(c => c.isFilter).map(c => c.index)
  const trs = document.querySelectorAll(`#myTable tr:not(.header)`)
  const filter = document.querySelector('#myInput').value
  const regex = new RegExp(escape(filter), 'i')
  const isFoundInTds = td => regex.test(td.innerHTML)
  const isFound = childrenArr => childrenArr.some(isFoundInTds)
  const setTrStyleDisplay = ({ style, children }) => {
    style.display = isFound([
      ...filterColumns.map(c => children[c]) // <-- filter Columns
    ]) ? '' : 'none'
  }
  
  trs.forEach(setTrStyleDisplay)
}
input#myInput { width: 220px; }
table#myTable { width: 100%; }
table#myTable th { text-align: left; padding: 20px 0 10px; }
<input 
  type="text" 
  id="myInput" 
  onkeyup="myFunction()" 
  placeholder="Search for names or countries.." 
  title="Type in a name or a country">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>
Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
  • 1
    What if you want to filter each column separately, and not search the entire table? – bart cubrich Oct 20 '21 at 17:45
  • @bartcubrich I have edited the answer to add support for filtering by `n` number of columns – Yosvel Quintero Oct 21 '21 at 04:31
  • 1
    Thanks, that totally works. I noticed, however, that if you have the table set to only display a few rows this only filters those rows. Any simple fix for that? I'm using this on the table: $(document).ready(function() { $('#permits').DataTable( { "pagingType": "full_numbers", "order": [[ 1, "asc" ]] } ); } ); – bart cubrich Oct 21 '21 at 16:21
18

You are almost there. All you needed to do was to create another for loop and iterate over all td elements in the row, and filter using them. By doing so, if you add any columns in future, the filter will continue to work.

In the snippet below, I have done that, and slightly modified the hiding logic. I am hiding all the rows to begin with, and if a match is found, I unhide it.

for (i = 1; i < tr.length; i++) {
    // Hide the row initially.
    tr[i].style.display = "none";

    td = tr[i].getElementsByTagName("td");
    for (var j = 0; j < td.length; j++) {
      cell = tr[i].getElementsByTagName("td")[j];
      if (cell) {
        if (cell.innerHTML.toUpperCase().indexOf(filter) > -1) {
          tr[i].style.display = "";
          break;
        } 
      }
    }
}

function myFunction() {
  var input, filter, table, tr, td, cell, i, j;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("myTable");
  tr = table.getElementsByTagName("tr");
  for (i = 1; i < tr.length; i++) {
    // Hide the row initially.
    tr[i].style.display = "none";
  
    td = tr[i].getElementsByTagName("td");
    for (var j = 0; j < td.length; j++) {
      cell = tr[i].getElementsByTagName("td")[j];
      if (cell) {
        if (cell.innerHTML.toUpperCase().indexOf(filter) > -1) {
          tr[i].style.display = "";
          break;
        } 
      }
    }
  }
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.." title="Type in a name">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>

Note: I would suggest using innerText instead of innerHTML for filtering. If you have HTML content in the cells, innerHTML might interfere with the filtering.

Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
  • how to show "no matches found" instead of showing empty table? – vignesh selvarathinam Jan 30 '20 at 09:52
  • @vigneshselvarathinam Create a variable to keep track of the number of rows you display, and at the end, if the count is zero, you can give an alert. – Nisarg Shah Jan 30 '20 at 12:12
  • @Nisarg I tried `innerText` like you suggested and it was actually much slower than `innerHTML`. Your code is nice and fast. (I have about 1000 rows that I filter). – Andrew Mar 18 '20 at 21:54
  • 2
    @Andrew Yeah I believe there are cases when innerHTML is faster (particularly with IE), but innerText is still preferable as it does not include the HTML markup. You can even try using textContent with this. – Nisarg Shah Mar 19 '20 at 04:25
  • I didn't test any performance, but I declare a variable visible = "none" instead of tr[i].style.display = "none";. Then, after checking the innerText, I set the variable visible = "". At the end of each iteration I set the row display attribute to equal the visible variable. In this way I avoid setting the same attribute twice. – Bogdan Prădatu Oct 29 '20 at 12:35
4

Only change

td = tr[i].getElementsByTagName("td")[0];

to

td = tr[i].getElementsByTagName("td")[1];

should work fine.

Update

Add all columns search.

function myFunction() {
  var input, filter, table, tr, td, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("myTable");
  tr = table.getElementsByTagName("tr");
  for (var i = 0; i < tr.length; i++) {
    var tds = tr[i].getElementsByTagName("td");
    var flag = false;
    for(var j = 0; j < tds.length; j++){
      var td = tds[j];
      if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
        flag = true;
      } 
    }
    if(flag){
        tr[i].style.display = "";
    }
    else {
        tr[i].style.display = "none";
    }
  }
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.." title="Type in a name">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>
Terry Wei
  • 1,521
  • 8
  • 16
3

With

td = tr[i].getElementsByTagName("td")[0];

you're only selecting the first td. Instead, check for if some of the tds have the string in question:

function myFunction() {
  const input = document.getElementById("myInput");
  const inputStr = input.value.toUpperCase();
  document.querySelectorAll('#myTable tr:not(.header)').forEach((tr) => {
    const anyMatch = [...tr.children]
      .some(td => td.textContent.toUpperCase().includes(inputStr));
    if (anyMatch) tr.style.removeProperty('display');
    else tr.style.display = 'none';
  });
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.." title="Type in a name">

<table id="myTable">
  <tr class="header">
    <th style="width:60%;">Name</th>
    <th style="width:40%;">Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Berglunds snabbkop</td>
    <td>Sweden</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Koniglich Essen</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Italy</td>
  </tr>
  <tr>
    <td>North/South</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Paris specialites</td>
    <td>France</td>
  </tr>
</table>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
3

Here is the code to create an HTML table with a filter option on each column separately. Trust me it is easy and works for me. Just give it a try. I am applying it to my JSON data.

For this, I give this credit to https://www.jqueryscript.net/table/filter-each-column.html

function checkval(){1==$("tbody tr:visible").length&&"No result found"==$("tbody tr:visible td").html()?$("#rowcount").html("0"):$("#rowcount").html($("tr:visible").length-1)}$(document).ready(function(){$("#rowcount").html($(".filterable tr").length-1),$(".filterable .btn-filter").click(function(){var t=$(this).parents(".filterable"),e=t.find(".filters input"),l=t.find(".table tbody");1==e.prop("disabled")?(e.prop("disabled",!1),e.first().focus()):(e.val("").prop("disabled",!0),l.find(".no-result").remove(),l.find("tr").show()),$("#rowcount").html($(".filterable tr").length-1)}),$(".filterable .filters input").keyup(function(t){if("9"!=(t.keyCode||t.which)){var e=$(this),l=e.val().toLowerCase(),n=e.parents(".filterable"),i=n.find(".filters th").index(e.parents("th")),r=n.find(".table"),o=r.find("tbody tr"),d=o.filter(function(){return-1===$(this).find("td").eq(i).text().toLowerCase().indexOf(l)});r.find("tbody .no-result").remove(),o.show(),d.hide(),d.length===o.length&&r.find("tbody").prepend($('<tr class="no-result text-center"><td colspan="'+r.find(".filters th").length+'">No result found</td></tr>'))}$("#rowcount").html($("tr:visible").length-1),checkval()})});
.filterable{margin-top:15px}.filterable .panel-heading .pull-right{margin-top:-20px}.filterable .filters input[disabled]{background-color:transparent;border:none;cursor:auto;box-shadow:none;padding:0;height:auto}.filterable .filters input[disabled]::-webkit-input-placeholder{color:#333}.filterable .filters input[disabled]::-moz-placeholder{color:#333}.filterable .filters input[disabled]:-ms-input-placeholder{color:#333}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="panel panel-primary filterable">
<table class="table">
  <thead>
    <tr class="filters">
      <th><input type="text" placeholder="#"></th>
      <th><input type="text" placeholder="First Name"></th>
      <th><input type="text" placeholder="Last Name"></th>
      <th><input type="text" placeholder="Username"></th>
      <th><input type="text" placeholder="PhoneNo"></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>10</td>
      <td>Tom</td>
      <td>Amar</td>
      <td>@TAmar</td>
      <td>306-456-7890</td>
    </tr>
    <tr>
      <td>20</td>
      <td>Dick</td>
      <td>Akbar</td>
      <td>@DAkbar</td>
      <td>456-456-7890</td>
    </tr>
    <tr>
      <td>30</td>
      <td>Harry</td>
      <td>Anthony</td>
      <td>@HAnthony</td>
      <td>526-456-7890</td>
    </tr>
  </tbody>
</table>
<p>No.of Rows : <span id="rowcount"></span></p>
</div>
K Dave
  • 31
  • 2
1

if you want to make filter to second column alone then a little modification to Yosvel Quintero Arguelles's code is given below

const table = document.getElementById("tableId");  
const trs = table.querySelectorAll('tr:nth-child(n)');  
// Or if you have headers, use line below instead of above
// const trs = document.querySelectorAll('#tableId tr:not(.header)');
const filter = document.querySelector('#myInput').value;
const regex = new RegExp(filter, 'i');
const isFoundInTds = (td) => regex.test(td.innerHTML);
const setTrStyleDisplay = ({ style, children }) => {
    // Here 1 represents second column
    style.display = isFoundInTds(children[1]) ? '' : 'none';
};

trs.forEach(setTrStyleDisplay);
Farhat Aziz
  • 131
  • 1
  • 10
1

Super simple solution. Case sensitive. All Columns.

Bonus: with debound function

document.getElementById('filter').addEventListener('keyup', debound(filter_table, 500))

function filter_table(e) {
  const rows = document.querySelectorAll('tbody tr')
  rows.forEach(row => {
    row.style.display = (row.innerText.includes(e.target.value)) ? '' : 'none'
  })
}

function debound(func, timeout) {
  let timer
  return (...args) => {
    if (!timer) {
      func.apply(this, args);
    }
    clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
      timer = undefined
    }, timeout)
  }
}
<input type="text" id="filter">
<table id="myTable">
  <thead>
    <tr class="header">
      <th style="width:60%;">Name</th>
      <th style="width:40%;">Country</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alfreds Futterkiste</td>
      <td>Germany</td>
    </tr>
    <tr>
      <td>Berglunds snabbkop</td>
      <td>Sweden</td>
    </tr>
    <tr>
      <td>Island Trading</td>
      <td>UK</td>
    </tr>
    <tr>
      <td>Koniglich Essen</td>
      <td>Germany</td>
    </tr>
    <tr>
      <td>Laughing Bacchus Winecellars</td>
      <td>Canada</td>
    </tr>
    <tr>
      <td>Magazzini Alimentari Riuniti</td>
      <td>Italy</td>
    </tr>
    <tr>
      <td>North/South</td>
      <td>UK</td>
    </tr>
    <tr>
      <td>Paris specialites</td>
      <td>France</td>
    </tr>
  </tbody>
</table>
Cesar Morillas
  • 707
  • 5
  • 11
0

You can also filter a table with js in this way Add a button which is handling click

 <button class="btn btn-secondary mx-2" 
  type="submit" 
  onclick="sortTableyear3()">3</button>

then add this function in your .js file or in the script tag

 function sortTableyear3() {
  var input, filter, table, tr, td, i, txtValue;
  input = 3;  // change this accordingly
  filter = input.value;
  table = document.getElementById("regstud-table");
  tr = table.getElementsByTagName("tr");

// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
    td = tr[i].getElementsByTagName("td")[2];// 2 is for 3rd column
    if (td) {
        txtValue = td.textContent || td.innerText;
        if (txtValue == input) {
            tr[i].style.display = "";
        } else {
            tr[i].style.display = "none";
        }
    }
}
  }

this will show only those rows in which third column values are 3. And with using greater than and filter in a table

Mohit kumar
  • 71
  • 1
  • 2