Rohit Girdhar
Rohit Girdhar

Reputation: 353

HTML Table Multiple Column Filters

I have a HTML table having three columns - (Name, Age, City). I'm trying to achieve 'MS Excel' like functionality, where I can filter multiple columns.

Although the filters are working individually, they malfunction when the user enters text in multiple input fields at once. For example, just entering the name would work fine, but entering the name along with the city, will completely rule out the name filter.

function nameSearch() {
  var input_name, input_age, input_city, filter, table, tr, td, i, txtValue_name, txtValue_age, txtValue_city;

  input_name = document.getElementById("name-search");
  input_age = document.getElementById("age-search");
  input_city = document.getElementById("city-search");

  filter_name = input_name.value.toUpperCase();
  filter_age = input_age.value.toUpperCase();
  filter_city = input_city.value.toUpperCase();


  table = document.getElementById("custom-table");
  tr = table.getElementsByTagName("tr");

  for (i = 0; i < tr.length; i++) {
    td_name = tr[i].getElementsByTagName("td")[0];
    if (td_name) {
      txtValue_name = td_name.textContent || td_name.innerText;
      if (txtValue_name.toUpperCase().indexOf(filter_name) > -1) {
        tr[i].style.display = "";
      } else {
        tr[i].style.display = "none";
      }

    }
  }
}

function ageSearch() {
  var input_name, input_age, input_city, filter, table, tr, td, i, txtValue_name, txtValue_age, txtValue_city;

  input_name = document.getElementById("name-search");
  input_age = document.getElementById("age-search");
  input_city = document.getElementById("city-search");

  filter_name = input_name.value.toUpperCase();
  filter_age = input_age.value.toUpperCase();
  filter_city = input_city.value.toUpperCase();


  table = document.getElementById("custom-table");
  tr = table.getElementsByTagName("tr");

  for (i = 0; i < tr.length; i++) {
    td_age = tr[i].getElementsByTagName("td")[1];
    if (td_age) {
      txtValue_age = td_age.textContent || td_age.innerText;
      if (txtValue_age.toUpperCase().indexOf(filter_age) > -1) {
        tr[i].style.display = "";
      } else {
        tr[i].style.display = "none";
      }

    }
  }
}

function citySearch() {
  var input_name, input_age, input_city, filter, table, tr, td, i, txtValue_name, txtValue_age, txtValue_city;

  input_name = document.getElementById("name-search");
  input_age = document.getElementById("age-search");
  input_city = document.getElementById("city-search");

  filter_name = input_name.value.toUpperCase();
  filter_age = input_age.value.toUpperCase();
  filter_city = input_city.value.toUpperCase();


  table = document.getElementById("custom-table");
  tr = table.getElementsByTagName("tr");

  for (i = 0; i < tr.length; i++) {
    td_city = tr[i].getElementsByTagName("td")[2];
    if (td_city) {
      txtValue_city = td_city.textContent || td_city.innerText;
      if (txtValue_city.toUpperCase().indexOf(filter_city) > -1) {
        tr[i].style.display = "";
      } else {
        tr[i].style.display = "none";
      }

    }
  }
}
table,
td,
th {
  border: 1px solid black;
  border-collapse: collapse;
  padding: 10px;
  margin-top: 20px;
}
<!DOCTYPE html>
<html>

<head>
  <title></title>
  <link rel="stylesheet" type="text/css" href="main.css">
</head>

<body>
  <input type="text" id="name-search" onkeyup="nameSearch()" placeholder="Name.." class="table-search-filters">
  <input type="text" id="age-search" onkeyup="ageSearch()" placeholder="Age.." class="table-search-filters">
  <input type="text" id="city-search" onkeyup="citySearch()" placeholder="City.." class="table-search-filters">
  <table id="custom-table">
    <thead>
      <th>Name</th>
      <th>Age</th>
      <th>City</th>
    </thead>
    <tbody>
      <tr>
        <td>Bruce</td>
        <td>32</td>
        <td>Gotham</td>
      </tr>
      <tr>
        <td>Bane</td>
        <td>32</td>
        <td>Chicago</td>
      </tr>
      <tr>
        <td>Joker</td>
        <td>28</td>
        <td>Gotham</td>
      </tr>
      <tr>
        <td>Harvey</td>
        <td>30</td>
        <td>Miami</td>
      </tr>
    </tbody>
  </table>
  <script type="text/javascript" src="script.js"></script>
</body>

</html>

Instead of having three seperate 'onkeyup' function, I also tried to map all the inputs to a single function, but that still didn't help much.

Upvotes: 0

Views: 12987

Answers (4)

Michael Santolini
Michael Santolini

Reputation: 1

If you want to include a header change to include class = header or change to

<tr class="header">
            <th>Name</th>
            <th>Age</th>
            <th>City</th>
</tr>

inside

and use this line of code in *.js file

tr = table.querySelectorAll("tbody tr:not(.header)");

table header missing after search

Upvotes: 0

Ivan Burnaev
Ivan Burnaev

Reputation: 2730

So, you need to create a filtering function for each filter. You can use startsWith, includes or === to achieve a different searching behaviours.

Next, you need to create a "main" filter which will call all other filters.

Then add an event listener to the parent element (in my snipped I added it to the window object) to prevent multiple event listeners. When event occurs check it's target and call main filter function it it's needed.

Some obvious features:

  • custom filtering behavious
  • pure functions which can be easily tested
  • composable main filter function
  • no imperative mess =) (debatable)

const sourceList = Array.from(document.querySelectorAll("tbody tr"));

const nameFilter = (value, item) => !value || item.querySelector("td:nth-child(1)").textContent.toLowerCase().includes(value.toLowerCase());

const ageFilter = (value, item) => !value || item.querySelector("td:nth-child(2)").textContent.startsWith(value);

const cityFilter = (value, item) => !value || item.querySelector("td:nth-child(3)").textContent.toLowerCase().includes(value.toLowerCase());

const mainFilter = ({name, age, city}, item) => {
  return nameFilter(name, item) && ageFilter(age, item) && cityFilter(city, item);
}

const currentFilters = {
  name: '',
  age: '',
  city: '',
};

window.addEventListener('input', event => {
  if (event.target.matches('.table-search-filters')) {
  
    currentFilters[event.target.name] = event.target.value;   
    sourceList.forEach(item => {
      const isVisible = mainFilter(currentFilters, item);

      item.style.display = !isVisible ? 'none' : 'inherit';
    })
  }

})

const table = document.querySelector('table');
<input name="name" type="text" id="name-search" placeholder="Name.." class="table-search-filters">
<input name="age" type="text" id="age-search" placeholder="Age.." class="table-search-filters">
<input name="city" type="text" id="city-search" placeholder="City.." class="table-search-filters">
    <table id="custom-table">
        <thead>
            <th>Name</th>
            <th>Age</th>
            <th>City</th>
        </thead>
        <tbody>
            <tr>
                <td>Bruce</td>
                <td>32</td>
                <td>Gotham</td>
            </tr>
            <tr>
                <td>Bane</td>
                <td>32</td>
                <td>Chicago</td>
            </tr>
            <tr>
                <td>Joker</td>
                <td>28</td>
                <td>Gotham</td>
            </tr>
            <tr>
                <td>Harvey</td>
                <td>30</td>
                <td>Miami</td>
            </tr>
        </tbody>
    </table>

Upvotes: 1

M A Salman
M A Salman

Reputation: 3820

No need of separate onKeyUp handlers for each input.Just one handler is enough.

Instead of getting elements by tag "td" tr[i].getElementsByTagName("td"), use tr[i].cells

and table.rows to get rows (From gman's comment)

instead of tr =table.getElementsByTagName("tr");

      tr = table.rows;
      for (let i = 0; i < tr.length; i++) {
        td= tr[i].cells;
        td_name =td[0].innerText;
        td_age = td[1].innerText;
        td_city = td[2].innerText;
          if (td_name.toUpperCase().indexOf(filter_name) > -1 && td_age.toUpperCase().indexOf(filter_age) > -1 && td_city.toUpperCase().indexOf(filter_city) > -1) {
            tr[i].style.display = "";
          } 
          else 
            tr[i].style.display = "none";
      }

var input_name = document.getElementById("name-search");
var input_age = document.getElementById("age-search");
var input_city = document.getElementById("city-search");
var table = document.getElementById("custom-table");

function search() {
  let filter_name = input_name.value.toUpperCase();
  let filter_age = input_age.value.toUpperCase();
  let filter_city = input_city.value.toUpperCase();
  let tr = table.rows;
  for (let i = 0; i < tr.length; i++) {
    td = tr[i].cells;
    td_name = td[0].innerText;
    td_age = td[1].innerText;
    td_city = td[2].innerText;
    if (td_name.toUpperCase().indexOf(filter_name) > -1 && td_age.toUpperCase().indexOf(filter_age) > -1 && td_city.toUpperCase().indexOf(filter_city) > -1) {
      tr[i].style.display = "";
    } else
      tr[i].style.display = "none";
  }
}
table,
td,
th {
  border: 1px solid black;
  border-collapse: collapse;
  padding: 10px;
  margin-top: 20px;
}
<!DOCTYPE html>
<html>

<head>
  <title></title>
  <link rel="stylesheet" type="text/css" href="main.css">
</head>

<body>
  <input type="text" id="name-search" onkeyup="search()" placeholder="Name.." class="table-search-filters">
  <input type="text" id="age-search" onkeyup="search()" placeholder="Age.." class="table-search-filters">
  <input type="text" id="city-search" onkeyup="search()" placeholder="City.." class="table-search-filters">
  <table id="custom-table">
    <thead>
      <th>Name</th>
      <th>Age</th>
      <th>City</th>
    </thead>
    <tbody>
      <tr>
        <td>Bruce</td>
        <td>32</td>
        <td>Gotham</td>
      </tr>
      <tr>
        <td>Bane</td>
        <td>32</td>
        <td>Chicago</td>
      </tr>
      <tr>
        <td>Joker</td>
        <td>28</td>
        <td>Gotham</td>
      </tr>
      <tr>
        <td>Harvey</td>
        <td>30</td>
        <td>Miami</td>
      </tr>
    </tbody>
  </table>

</body>

</html>

Upvotes: 6

mosharaf13
mosharaf13

Reputation: 346

You can combine your three function into one and just check conditions with and(&&). hope below code helps.

index.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
        <input type="text" id="name-search" onkeyup="search()" placeholder="Name.." class="table-search-filters">
        <input type="text" id="age-search" onkeyup="search()" placeholder="Age.." class="table-search-filters">
         <input type="text" id="city-search" onkeyup="search()" placeholder="City.." class="table-search-filters">
    <table id="custom-table">
        <thead>
            <th>Name</th>
            <th>Age</th>
            <th>City</th>
        </thead>
        <tbody>
            <tr>
                <td>Bruce</td>
                <td>32</td>
                <td>Gotham</td>
            </tr>
            <tr>
                <td>Bane</td>
                <td>32</td>
                <td>Chicago</td>
            </tr>
            <tr>
                <td>Joker</td>
                <td>28</td>
                <td>Gotham</td>
            </tr>
            <tr>
                <td>Harvey</td>
                <td>30</td>
                <td>Miami</td>
            </tr>
        </tbody>
    </table>
    <script type="text/javascript" src="script.js"></script>
</body>
</html>

script.js

function search() {
    var input_name, input_age, input_city, filter, table, tr, td, i, txtValue_name, txtValue_age, txtValue_city;

    input_name = document.getElementById("name-search");
    input_age = document.getElementById("age-search");
    input_city = document.getElementById("city-search");

    filter_name = input_name.value.toUpperCase();
    filter_age = input_age.value.toUpperCase();
    filter_city = input_city.value.toUpperCase();


    table = document.getElementById("custom-table");
    tr = table.getElementsByTagName("tr");

    for (i = 0; i < tr.length; i++) {
        td_city = tr[i].getElementsByTagName("td")[2];
        td_age = tr[i].getElementsByTagName("td")[1];
        td_name = tr[i].getElementsByTagName("td")[0];

        if(td_city && td_age && td_name){
            txtValue_city = td_city.textContent || td_city.innerText;
            txtValue_age = td_age.textContent || td_age.innerText;
            txtValue_name = td_name.textContent || td_name.innerText;

            if (txtValue_city.toUpperCase().indexOf(filter_city) > -1
                && txtValue_age.toUpperCase().indexOf(filter_age) > -1
                && txtValue_name.toUpperCase().indexOf(filter_name) > -1) {
                tr[i].style.display = "";
            }
            else {
                tr[i].style.display = "none";
            }
        }
    }
}

Upvotes: 1

Related Questions