skip to Main Content

I have an HTML table that uses the rowspan attribute. I’ve added a search feature to the table, but when I search for something like "Washington," it removes the values "Monkey" and "Lion" from the ABC column and replaces them with values from the DEF column. I’m not sure how to fix this. Do you have any advice? Here is my code –

function searchTable() {
  var input, filter, table, tr, td, i, j, txtValue, match;
  input  = document.getElementById("searchInput");
  filter = input.value.toUpperCase();
  table  = document.getElementById("example_full");
  tr     = table.getElementsByTagName("tr");
  match  = false;

  for (i = 1; i < tr.length; i++) {    // Skip the header row
    tr[i].style.display = "none";      // Hide all rows initially
    td                  = tr[i].getElementsByTagName("td");
    for (j = 0; j < td.length; j++) {
      if (td[j]) {
        txtValue = td[j].textContent || td[j].innerText;
        if (txtValue.toUpperCase().indexOf(filter) > -1) {
          tr[i].style.display = "";
          match = true;
          break;
        }
      }
    }
  }

  // Show or hide the no match message
  document.getElementById("noMatch").style.display = match ? "none" : "block";
}
.no-match {
  display : none;
  color   : red;
}

th {
  border-left   : 1px solid #dddddd;
  border-top    : 1px solid #dddddd;
  border-bottom : 1px solid #dddddd;
  border-right  : 1px solid #dddddd;
  background-color: darkgray;
}
<input type="text" id="searchInput" placeholder="Search for records..." onkeyup="searchTable()" />
<p id="noMatch" class="no-match">No matching records found</p>

<table id="example_full">
  <thead>
    <tr>
      <th>ABC</th>
      <th>DEF</th>
      <th>GHI</th>
      <th>XXX</th>
      <th>MMM</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="4" style=" vertical-align: middle;">MONKEY</td>
      <td>AD123</td>
      <td>434SDD</td>
      <td>DFDDFF</td>
      <td>FDFFD</td>
    </tr>
    <tr>
      <td>LEMON</td>
      <td> </td>
      <td>FDFGGF</td>
      <td>DFFDF</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>FDDFF</td>
      <td>Edison</td>
    </tr>
    <tr>
      <td>FDDFFDF</td>
      <td> </td>
      <td>East</td>
      <td>Washington</td>
    </tr>
    <tr>
      <td rowspan="3" style=" background-color: #F9F9F9; vertical-align: middle;">LION</td>
      <td>GFFG</td>
      <td>FGFGG</td>
      <td>FGFG</td>
      <td>FGGFGFG</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>FGHGH</td>
      <td>DDDFF</td>
    </tr>
    <tr>
      <td></td>
      <td>test2</td>
      <td>FFGGFH</td>
      <td>Washington</td>
    </tr>
  </tbody>
</table>

3

Answers


  1. Make sure each row has 5 cells. In your case you collapse to 4 cells on rows that do not have the rowspan

    Here is a more streamlined version using imhvost’s suggestion to collapse the visibility instead of display:none/hidden

    const input = document.getElementById("searchInput");
    const noMatch = document.getElementById("noMatch");
    const rows = document.querySelectorAll("#example_full tbody tr");
    const searchTable = () => {
      let filter = input.value.toUpperCase(),
      match = false;
      rows.forEach(row => {
        let found = filter && Array.from(row.querySelectorAll('td')).some(td => td.textContent.toUpperCase().includes(filter));
        row.style.visibility = filter && !found ? 'collapse' : 'visible';
        if (found) match = true; // set if any hit
      });  
      // Show or hide the no match message
       noMatch.hidden = !filter || (filter && match);
    }
    input.addEventListener('input',searchTable);
    .no-match {
      color: red;
    }
    
    th {
      border-left: 1px solid #dddddd;
      border-top: 1px solid #dddddd;
      border-bottom: 1px solid #dddddd;
      border-right: 1px solid #dddddd;
      background-color: darkgray;
    }
    <input type="text" id="searchInput" placeholder="Search for records..." />
    <p id="noMatch" hidden class="no-match">No matching records found</p>
    
    <table id="example_full">
      <thead>
        <tr>
          <th>ABC</th>
          <th>DEF</th>
          <th>GHI</th>
          <th>XXX</th>
          <th>MMM</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td rowspan="4" style=" vertical-align: middle;">MONKEY</td>
          <td>AD123</td>
          <td>434SDD</td>
          <td>DFDDFF</td>
          <td>FDFFD</td>
        </tr>
    
        <tr>
          <td>LEMON</td>
          <td>&#160;</td>
          <td>FDFGGF</td>
          <td>DFFDF</td>
        </tr>
    
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FDDFF</td>
          <td>Edison</td>
        </tr>
    
        <tr>
          <td>FDDFFDF</td>
          <td>&#160;</td>
          <td>East</td>
          <td>Washington</td>
        </tr>
    
    
    
        <tr>
          <td rowspan="3" style=" background-color: #F9F9F9; vertical-align: middle;">LION</td>
          <td>GFFG</td>
          <td>FGFGG</td>
          <td>FGFG</td>
          <td>FGGFGFG</td>
        </tr>
    
    
    
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FGHGH</td>
          <td>DDDFF</td>
        </tr>
    
        <tr>
          <td></td>
          <td>test2</td>
          <td>FFGGFH</td>
          <td>Washington</td>
        </tr>
    
      </tbody>
    </table>
    Login or Signup to reply.
  2. Use tr { visibility: collapse; } instead of tr { display: none; }:

    document.getElementById('search-input').addEventListener('keyup', searchTable);
    
    function searchTable() {
      const filter = this.value.toUpperCase();
      let isMatch = false;
    
      const tbody = document.querySelector('#example_full tbody');
      const noMatch = document.getElementById('no-match');
    
      if (!filter) {
        tbody.querySelectorAll('tr').forEach(tr => {
          tr.classList.remove('collapse', 'hidden');
        });
        noMatch.classList.remove('active');
        return;
      }
    
      let rowspanEndIndex = 0;
    
      tbody.querySelectorAll('tr').forEach((tr, index) => {
        tr.classList.add('collapse');
        tr.classList.remove('hidden');
    
        if (rowspanEndIndex === index) {
          rowspanEndIndex = 0;
        }
        tr.querySelectorAll('td').forEach(td => {
          const rowspan = td.getAttribute('rowspan');
          if (rowspan) {
            rowspanEndIndex = index + Number(rowspan);
          }
          if (td.textContent && td.textContent.toUpperCase().includes(filter)) {
            tr.classList.remove('collapse');
            isMatch = true;
          }
        });
        if (rowspanEndIndex) {
          tr.setAttribute('data-group', rowspanEndIndex);
        }
      });
      document
        .querySelectorAll('#example_full tbody tr:has([rowspan])')
        .forEach(tr => {
          const group = tr.getAttribute('data-group');
          const trs = tbody.querySelectorAll(`tr[data-group="${group}"]`).length;
          const collapseTrs = tbody.querySelectorAll(
            `tr[data-group="${group}"].collapse`,
          ).length;
          if (trs !== collapseTrs) {
            tr.classList.add('hidden');
          }
        });
      noMatch.classList.toggle('active', !isMatch);
    }
    .no-match {
      color: red;
      &:not(.active) {
        display: none;
      }
    }
    
    table {
      border-collapse: collapse;
    }
    
    td,
    th {
      padding: 2px 8px;
    }
    
    th {
      border: 1px solid #dddddd;
      background-color: darkgray;
    }
    
    tr {
      &.collapse {
        visibility: collapse;
        &.hidden {
          visibility: visible;
          td:not([rowspan]) {
            display: none;
          }
        }
      }
    }
    <input
      type="text"
      id="search-input"
      placeholder="Search for records..."
    />
    <p
      id="no-match"
      class="no-match"
    >
      No matching records found
    </p>
    
    <table id="example_full">
      <thead>
        <tr>
          <th>ABC</th>
          <th>DEF</th>
          <th>GHI</th>
          <th>XXX</th>
          <th>MMM</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td
            rowspan="4"
            style="vertical-align: middle"
          >
            MONKEY
          </td>
          <td>AD123</td>
          <td>434SDD</td>
          <td>DFDDFF</td>
          <td>FDFFD</td>
        </tr>
        <tr>
          <td>LEMON</td>
          <td>&#160;</td>
          <td>FDFGGF</td>
          <td>DFFDF</td>
        </tr>
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FDDFF</td>
          <td>Edison</td>
        </tr>
        <tr>
          <td>FDDFFDF</td>
          <td>&#160;</td>
          <td>East</td>
          <td>LEMON</td>
        </tr>
        <tr>
          <td>One tr</td>
          <td>&#160;</td>
          <td>LEMON</td>
          <td>DDDFF</td>
        </tr>
        <tr>
          <td
            rowspan="3"
            style="background-color: #f9f9f9; vertical-align: middle"
          >
            LION
          </td>
          <td>GFFG</td>
          <td>FGFGG</td>
          <td>FGFG</td>
          <td>FGGFGFG</td>
        </tr>
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FGHGH</td>
          <td>DDDFF</td>
        </tr>
        <tr>
          <td></td>
          <td>test2</td>
          <td>FFGGFH</td>
          <td>Washington</td>
        </tr>
      </tbody>
    </table>
    Login or Signup to reply.
  3. Per your comment ‘Is there a way to still show the value for column ABC if I search "Lemon", here is a solution that will create and delete cells and labels as needed. I tried to add some basic comments to the js code to help explain what is going on.

    function searchTable() {
      var input, filter, table, tr, th, td, i, j, txtValue, match, rowSpan, rowSpanData;
       input  = document.getElementById("searchInput");
       filter = input.value.toUpperCase();
       table  = document.getElementById("example_full");
       tr     = table.getElementsByTagName("tr");
       th     = table.getElementsByTagName("th");
       match  = false;
    
      rowSpan = 1;
      rowSpanData = '';
    
      for (i = 1; i < tr.length; i++) {    // Skip the header row
        tr[i].style.display = "none";      // Hide all rows initially
        td                  = tr[i].getElementsByTagName("td");
        //console.log("TD Length = " + td.length);
        
        //Loop through the cells
        for (j = 0; j < td.length; j++) {
    
        if (td[j]) {
        
        if(filter!="") {
            if(j==0 && td.length == th.length){
              rowSpanData = td[j].innerHTML;
            }
            
            //If there is a match 
            txtValue = td[j].textContent || td[j].innerText;
            if (txtValue.toUpperCase().indexOf(filter) > -1) {
               
                 tr[i].style.display = "";
                 //match boolean to be used for display of match message at end of fucntion
                 match = true;
               
    
              if (td.length == th.length && td[0].hasAttribute("rowspan") && td[0].getAttribute("rowspan") != 1) {
                td[0].setAttribute("originalRowSpan", td[0].getAttribute("rowspan"));
                td[0].setAttribute("rowspan", 1);
              }
              if(td.length < th.length){
                 var newCell = tr[i].insertCell(0);
                 newCell.classList.add("removeMe");
                 newCell.innerHTML= rowSpanData;
              }
              // skip to the next row
              continue;
            } //end of match section
    
            } else {
              tr[i].style.display = "";
                if(td[j].hasAttribute("rowspan")){
                  td[j].setAttribute("rowspan", td[j].getAttribute("originalRowSpan"));
                } else if(td[j].classList.contains("removeMe")){
                  tr[i].deleteCell(0);
                }
            }
              
          } 
        } // end of cell loop
      } // end of row loop
    
      // Show or hide the no match message
      document.getElementById("noMatch").style.display = match ? "none" : "block";
    }
    .no-match {
      display : none;
      color   : red;
    }
    
    th {
      border-left   : 1px solid #dddddd;
      border-top    : 1px solid #dddddd;
      border-bottom : 1px solid #dddddd;
      border-right  : 1px solid #dddddd;
      background-color: darkgray;
    }
    <input type="text" id="searchInput" placeholder="Search for records..." onkeyup="searchTable()" />
    <p id="noMatch" class="no-match">No matching records found</p>
    
    <table id="example_full">
      <thead>
        <tr>
          <th>ABC</th>
          <th>DEF</th>
          <th>GHI</th>
          <th>XXX</th>
          <th>MMM</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td rowspan="4" style=" vertical-align: middle;">MONKEY</td>
          <td>AD123</td>
          <td>434SDD</td>
          <td>DFDDFF</td>
          <td>FDFFD</td>
        </tr>
        <tr>
          <td>LEMON</td>
          <td>&#160;</td>
          <td>FDFGGF</td>
          <td>DFFDF</td>
        </tr>
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FDDFF</td>
          <td>Edison</td>
        </tr>
        <tr>
          <td>FDDFFDF</td>
          <td>&#160;</td>
          <td>East</td>
          <td>Washington</td>
        </tr>
        <tr>
          <td rowspan="3" style=" background-color: #F9F9F9; vertical-align: middle;">LION</td>
          <td>GFFG</td>
          <td>FGFGG</td>
          <td>FGFG</td>
          <td>FGGFGFG</td>
        </tr>
        <tr>
          <td>&#160;</td>
          <td>&#160;</td>
          <td>FGHGH</td>
          <td>DDDFF</td>
        </tr>
        <tr>
          <td></td>
          <td>test2</td>
          <td>FFGGFH</td>
          <td>Washington</td>
        </tr>
      </tbody>
    </table>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search