javascriptdomhtml-table

How to filer multiple TDs with one input text box only


I have a table with six columns containing approximately 7,000 entries. Currently, the table is filtered by six separate input text boxes, each with its own JavaScript function. Each function targets a specific column using code like:

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

Due to having multiple scripts on the page, it becomes unresponsive and takes a long time to load. To solve this performance issue, is it possible to filter the table with only one input box instead of six, but with a dropdown/select box to choose which column (td[0], td[1], td[2], etc.) to filter?

function filterTable() {
  var input, filter, table, tr, td, i, txtValue;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("myTable");
  tr = table.getElementsByTagName("tr");
  rowsFound = [];

  for (i = 0; i < tr.length; i++) {
    td = tr[i].getElementsByTagName("td")[4];
    if (td) {
      txtValue = td.textContent || td.innerText;
      if (txtValue.toUpperCase().indexOf(filter) == 0) {
        tr[i].style.display = "";
        rowsFound.push(1);
      } else {
        tr[i].style.display = "none";
      }
    }
  }
}
<span style="float:left">Total/Searched:</span>
<div id="statistic" style="float:left"></div>
<br><br><br>

<input type="text" id="myInput" onkeyup="filterTable()" onClick="filterTable()" placeholder="Search...">

<table id="myTable">
  <thead>
    <tr>
      <th>Name</th>
      <th>Country</th>
      <th>City</th>
      <th>Street</th>
      <th>House No</th>
      <th>Neighbour</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mario Resende</td>
      <td>Argentina</td>
      <td>Buenos Aires</td>
      <td>Belgrano Calle</td>
      <td>2:3</td>
      <td>Cristina Rodriguez</td>
    </tr>
    <tr>
      <td>Philips Douglas</td>
      <td>United Kingdom</td>
      <td>London</td>
      <td>Oxford Avenue</td>
      <td>17:25</td>
      <td>Sophie Loren</td>
    </tr>
    <tr>
      <td>Ramesh Bugatapa</td>
      <td>India</td>
      <td>New Delhi</td>
      <td>Golochand road</td>
      <td>1:2</td>
      <td>Kiran Johr</td>
    </tr>
  </tbody>
</table>


Solution

  • I think you can achieve better performance by using the property hierarchy of HTML tables.

    for (let row of myTable.tBodies[0].rows) 
      { ... }
    

    And to test this, also avoid using string manipulation functions. The regular expression system is optimized for this.

    rExp.test(row.cells[cIndex].textContent)
    

    Otherwise, I suggest clicking directly on the column header to launch a filter (rather than using a select with the same information)

    The code may look like this:

    const
      txt2search    = document.querySelector('#txt-2-search')
    , searchResult  = document.querySelector('#search-result')
    , myTable       = document.querySelector('#my-table')
    , searchInfo    = { cIndex: null, rExp: null, count:0, rFound:0 }
      ;
    searchResult.textContent 
      = searchInfo.count
      = myTable.tBodies[0].rows.length
      ;
    function showALL_elements() {
      myTable.tBodies[0].querySelectorAll('tr.noDisplay').forEach( TR =>
        {
        TR.classList.remove('noDisplay');
        })
      searchResult.textContent = searchInfo.count;
      }  
    myTable.tHead.onclick = ({target:xTH}) => {
      if (!xTH.matches('th'))  // this is a bad click...!
        return
        ;
      if (xTH.classList.toggle('cFilter')) // this a go-to-do filter
        {
        if (searchInfo.cIndex !== null)
          xTH.closest('tr').cells[searchInfo.cIndex].classList.remove('cFilter')
          ;
        searchInfo.cIndex = xTH.cellIndex;
        
        // Normally you have to be sure of the validity of the filtering criteria 
        // (no empty string, no special characters, etc.)
        searchInfo.rFound = searchInfo.count;
        searchInfo.rExp   = new RegExp(`^${txt2search.value.trim()}`, 'i');
        
        for (let row of myTable.tBodies[0].rows)
          {
          if ( row.classList.toggle
               ( 'noDisplay'
               , !searchInfo.rExp.test(row.cells[searchInfo.cIndex].textContent)
             ) )
            searchInfo.rFound--
            ;
          } // end loop for
        searchResult.textContent = `${searchInfo.rFound} / ${searchInfo.count}`;
        }
      else
        {
        searchInfo.cIndex = null;
        showALL_elements();
        }
      }
    * {
      margin  : 0;
      padding : 0;
      }
    body {
      background  : lavender;
      font-family : Geneva, sans-serif;
      font-size   : 16px;
      }
    table { 
      border-collapse : separate;
      border-spacing  : 1px;
      background      : darkblue;
      margin          : 1em; 
      }
    td,th      { padding    : .2em .8em;  }
    td         { background : whitesmoke; }  
    th         { background : #9bbad8;    cursor : zoom-in; }
    th.cFilter { background : orange;     }
    .noDisplay { display    : none;       }
    
    caption {
      text-align : left;
      padding    : .4rem;
      font-size  : 1.4rem;
      background : #a0dbdd;
      & input { 
        font-size  : 1.2rem; 
        }
      & #search-result {
        float     : right;
        font-size : .9rem;
        }  
      }
    <table id="my-table">
      <caption> 
        Find :
        <input type="text" id="txt-2-search" placeholder="then, select a column..." />
        <span id="search-result">0</span>
      </caption>
      <thead>
        <tr>
          <th>Name</th>
          <th>Country</th>
          <th>City</th>
          <th>Street</th>
          <th>House No</th>
          <th>Neighbour</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Mario Resende</td>
          <td>Argentina</td>
          <td>Buenos Aires</td>
          <td>Belgrano Calle</td>
          <td>2:3</td>
          <td>Cristina Rodriguez</td>
        </tr>
        <tr>
          <td>Philips Douglas</td>
          <td>United Kingdom</td>
          <td>London</td>
          <td>Oxford Avenue</td>
          <td>17:25</td>
          <td>Sophie Loren</td>
        </tr>
        <tr>
          <td>Ramesh Bugatapa</td>
          <td>India</td>
          <td>New Delhi</td>
          <td>Golochand road</td>
          <td>1:2</td>
          <td>Kiran Johr</td>
        </tr>
      </tbody>
    </table>