Search code examples
javascripthtmlhtml-table

JS not working expected with nested table


in below HTML, i have JS script to apply filter (filterTable), filter works well for columns "ID", "Invoice Number", 'date', "Name", "Email", "Phone number", "Address" and "Items" but it is not working for "Tax", "Invoice Total" and "Logged by".

But strangely, if I fill column "Items" with normal data instead of nested table then filterTable works expected for column "Tax", "Invoice Total" and "Logged by" as well.

In Short if I removed the nested table from "Items" then (filterTable) works well for column "Tax", "Invoice Total" and "Logged by".

Adding snapshot for reference (here filter works well for "ID", "Invoice Number", 'date', "Name", "Email", "Phone number", "Address" and "Items" but not working for columns "Tax", "Invoice Total" and "Logged by", instead when I search in "Tax" it is finding in 'items' column. But if I remove the nested table from 'items' columns then all the columns in the table work expected. enter image description here

Here is some sample rows from the table enter image description here

function updateDate(input) {
  if (!input) {
    filterTable(2, '');
    return;
  }
  const [year, month, day] = input.split('-');
  const monthAbbreviations = [
    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
  ];
  const formattedDate = `${day}-${monthAbbreviations[Number(month) - 1]}-${year}`;
  filterTable(2, formattedDate);
}

function filterTable(columnIndex, filterValue) {
  const table = document.querySelector('table');
  const rows = table.querySelectorAll('tbody tr');
  for (let i = 0; i < rows.length; i++) {
    const cells = rows[i].querySelectorAll('td');
    if (cells.length > columnIndex) {
      const cellValue = cells[columnIndex].textContent.toString().toLowerCase();
      console.log('cellValue:', cellValue);
      console.log('filterValue:', filterValue);
      console.log('columnindex:', columnIndex);
      if (cellValue.includes(filterValue.toString().toLowerCase())) {
        rows[i].style.display = '';
      } else {
        rows[i].style.display = 'none';
      }
    }
  }
}
<table class="table table-striped">
  <thead>
    <tr>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="ID" onkeyup="filterTable(0, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Invoice Number" onkeyup="filterTable(1, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="date" oninput="updateDate(this.value)"><span id="formatted-date"></span></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Name" onkeyup="filterTable(3, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Email" onkeyup="filterTable(4, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Phone number" onkeyup="filterTable(5, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Address" onkeyup="filterTable(6, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Items" onkeyup="filterTable(7, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Tax" onkeyup="filterTable(14, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Invoice Total" onkeyup="filterTable(9, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Logged by" onkeyup="filterTable(10, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;">Invoice</th>
    </tr>
  </thead>
  <tbody>
    {% for row in rows %}
    <tr>
      <td style="border: 1px solid black; padding: 10px;">{{ row[0] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[1] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[2] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[3] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[4] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[5] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[6] }}</td>
      <td style="border: 1px solid black; padding: 10px;">
        <table class="table table-striped" style="border-collapse: collapse; width: 100%;">
          <thead>
            <tr>
              <th style="border: 1px solid black; padding: 10px;">Description</th>
              <th style="border: 1px solid black; padding: 10px;">Price</th>
              <th style="border: 1px solid black; padding: 10px;">Qty</th>
              <th style="border: 1px solid black; padding: 10px;">Final Price</th>
            </tr>
          </thead>
          <tbody>
            {% set items = js.loads(row[7]) %} {% for item in items %}
            <tr>
              <td style="border: 1px solid black; padding: 10px;">{{ item.description }}</td>
              <td style="border: 1px solid black; padding: 10px;">{{ item.price }}</td>
              <td style="border: 1px solid black; padding: 10px;">{{ item.qty }}</td>
              <td style="border: 1px solid black; padding: 10px;">{{ item.final_price }}</td>
            </tr>
            {% endfor %}
          </tbody>
        </table>
      </td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[8] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[9] }}</td>
      <td style="border: 1px solid black; padding: 10px;">{{ row[10] }}</td>
      <td style="border: 1px solid black; padding: 10px;"><a href="{{ url_for('download', id=row[0]) }}">Download</a></td>
    </tr>
    {% endfor %}
  </tbody>
</table>
</div>

Example Code to reproduce the error

         function filterTable(columnIndex, filterValue) {
     const table = document.querySelector('table');
     const rows = table.querySelectorAll('tbody tr');
     for (let i = 0; i < rows.length; i++) {
         const cells = rows[i].querySelectorAll('td');
         if (cells.length > columnIndex) {
             const cellValue = cells[columnIndex].textContent.toString().toLowerCase();
             //console.log('cellValue:', cellValue);
             //console.log('filterValue:', filterValue);
             //console.log('columnindex:', columnIndex);
             if (cellValue.includes(filterValue.toString().toLowerCase())) {
                 rows[i].style.display = '';
             } else {
                 rows[i].style.display = 'none';
             }
         }
     }
     }
<table id="myTable">
  <thead>
    <tr>
      <th class='nested' style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="test" onkeyup="filterTable(0, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Tax" onkeyup="filterTable(1, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Invoice Total" onkeyup="filterTable(2, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Logged by" onkeyup="filterTable(3, this.value)"></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <table class="table table-striped" style="border-collapse: collapse; width: 100%;">
            <thead>
                <tr>
                    <th style="border: 1px solid black; padding: 10px;">Description</th>
                    <th style="border: 1px solid black; padding: 10px;">Price</th>
                    <th style="border: 1px solid black; padding: 10px;">Qty</th>
                    <th style="border: 1px solid black; padding: 10px;">Final Price</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td style="border: 1px solid black; padding: 10px;">test1</td>
                    <td style="border: 1px solid black; padding: 10px;">10</td>
                    <td style="border: 1px solid black; padding: 10px;">50</td>
                    <td style="border: 1px solid black; padding: 10px;">5000</td>
                </tr>
            </tbody>
        </table>
      </td>
     
      <td>10%</td>
      <td>$100</td>
      <td>John Doe</td>
    </tr>
    <tr>
      <td>
        <table class="table table-striped" style="border-collapse: collapse; width: 100%;">
            <thead>
                <tr>
                    <th style="border: 1px solid black; padding: 10px;">Description</th>
                    <th style="border: 1px solid black; padding: 10px;">Price</th>
                    <th style="border: 1px solid black; padding: 10px;">Qty</th>
                    <th style="border: 1px solid black; padding: 10px;">Final Price</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td style="border: 1px solid black; padding: 10px;">test2</td>
                    <td style="border: 1px solid black; padding: 10px;">10</td>
                    <td style="border: 1px solid black; padding: 10px;">50</td>
                    <td style="border: 1px solid black; padding: 10px;">5000</td>
                </tr>
            </tbody>
        </table>
      </td>

      <td>5%</td>
      <td>$50</td>
      <td>Jane Doe</td>
    </tr>
    <tr>
      <td>
        <table class="table table-striped" style="border-collapse: collapse; width: 100%;">
            <thead>
                <tr>
                    <th style="border: 1px solid black; padding: 10px;">Description</th>
                    <th style="border: 1px solid black; padding: 10px;">Price</th>
                    <th style="border: 1px solid black; padding: 10px;">Qty</th>
                    <th style="border: 1px solid black; padding: 10px;">Final Price</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td style="border: 1px solid black; padding: 10px;">test3</td>
                    <td style="border: 1px solid black; padding: 10px;">10</td>
                    <td style="border: 1px solid black; padding: 10px;">50</td>
                    <td style="border: 1px solid black; padding: 10px;">5000</td>
                </tr>
            </tbody>
        </table>
      </td>
      
      <td>15%</td>
      <td>$200</td>
      <td>Bob Smith</td>
    </tr>
  </tbody>
</table>

Below code can use to see that there is no error if table does not have nested table

         function filterTable(columnIndex, filterValue) {
     const table = document.querySelector('table');
     const rows = table.querySelectorAll('tbody tr');
     for (let i = 0; i < rows.length; i++) {
         const cells = rows[i].querySelectorAll('td');
         if (cells.length > columnIndex) {
             const cellValue = cells[columnIndex].textContent.toString().toLowerCase();
             //console.log('cellValue:', cellValue);
             //console.log('filterValue:', filterValue);
             //console.log('columnindex:', columnIndex);
             if (cellValue.includes(filterValue.toString().toLowerCase())) {
                 rows[i].style.display = '';
             } else {
                 rows[i].style.display = 'none';
             }
         }
     }
     }
<table id="myTable">
  <thead>
    <tr>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Tax" onkeyup="filterTable(0, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Invoice Total" onkeyup="filterTable(1, this.value)"></th>
      <th style="border: 1px solid black; padding: 10px;"><input type="text" placeholder="Logged by" onkeyup="filterTable(2, this.value)"></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>10%</td>
      <td>$100</td>
      <td>John Doe</td>
    </tr>
    <tr>
      <td>5%</td>
      <td>$50</td>
      <td>Jane Doe</td>
    </tr>
    <tr>
      <td>15%</td>
      <td>$200</td>
      <td>Bob Smith</td>
    </tr>
  </tbody>
</table>


Solution

  • Finally, I managed to fix this issue. I changed

    const cells = rows[i].querySelectorAll('td')

    with

    const cells = rows[i].querySelectorAll('td:not(.table-data table td)')

    and now it is working well.

    The issue was because querySelectorAll('td') was selecting all the cells in the row, including the nested table cells, and not just the cells in the column I was trying to filter. Means that when I try to access the text Content of the cell in the specified column index using cells[columnIndex].textContent, I was actually getting the text content of all the cells in that row, including the cells in the nested table. Now Modified code only selects the cells in the current row that are not in the nested table.