skip to Main Content

I have a table that i need to filter based on multiple columns, the example i have uses checkboxes to filter on 5 columns, some of the columns are hidden and are not displayed in the table but can still be filtered on.

Example of how I need it to work:

  • all rows will show as a default if no filters are selected, currently the table is blank by default, and you need to check the boxes to show rows.

  • if MONTH filter ‘MAR’ is checked, the 4 rows that meet that criteria will show, I then add the LANGUAGE filter ‘French’ and ‘Spanish’ and the only the 3 rows that meet both filter criteria will show. I then uncheck the LANGUAGE filters and check the HOUSE filter ‘None’ only the 1 row that meets the new criteria is shown.

There are a few problems in my current attempt like the filter categories are not correctly ANDed together, so when ‘Ron’ and ‘Mar’ are selected the results include other Names that do not match ‘Ron’. Another issue it that when the filters are cleared, many rows that were hidden due to the previous filter do not become visible.

Usable example is here:
https://codepen.io/ashleigh-leader/pen/BaXvOqO

function filter(event, filterCol) {
  let element = event.target;
  let condt1 = document.getElementsByClassName(filterCol);
  for (let i = 0; i < condt1.length; i++) {
    if (condt1[i].innerHTML.toLowerCase() == element.value.toLowerCase()) {
      if (element.checked == true) {
        condt1[i].parentElement.style = ""
      } else {
        condt1[i].parentElement.style = "display:none"
      }
    }
  }
}

document.querySelectorAll('.option1')
  .forEach(input => input.addEventListener('input', ()=>filter(event,"check1")));
  
  
document.querySelectorAll('.option2')
  .forEach(input => input.addEventListener('input', ()=>filter(event,"check2")));
  
  
document.querySelectorAll('.option3')
  .forEach(input => input.addEventListener('input', ()=>filter(event,"check3")));
  
  
document.querySelectorAll('.option4')
  .forEach(input => input.addEventListener('input', ()=>filter(event,"check4")));
  
  
document.querySelectorAll('.option5')
  .forEach(input => input.addEventListener('input', ()=>filter(event,"check5")));
#myTable {
border-collapse: collapse;
width: 100%; 
border: 1px solid #ddd;
font-size: 14px;
margin: 5px;
}
  
#myTable th, #myTable td {
text-align: left;
padding: 12px;
}
  
#myTable tr {
border-bottom: 1px solid #ddd;
color: #58585B;
font-family: "museo-sans-rounded";
font-size: 14px;
font-weight: 300px;
}
  
#myTable tr.header {
background-color: #862B90;
background-size: contain;
color: white;
font-family: "museo-sans-rounded";
font-size: 16px;
font-weight: 300px;
}

#myTable tr:hover {
font-family: "museo-sans-rounded";
font-size: 16px;
font-weight: 300px;
}

#myTable td.month-pill {
align-self: center;
background-color: #FFC52E;
border: none;
color: #FFC52E;
padding: 10px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px;
border-radius: 16px;
}

#myTable td.month {
color: #FFC52E;
align-items: center;
}

.filter-container {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 20%;
}

.filter-title {
font-family: "museo-sans-rounded";
color: #58585B !important;
font-size: 14px;
font-weight: 500px;
line-height: 1.25;
text-align: left;
}

.filter-option {
font-family: "museo-sans-rounded";
color: #58585B !important;
font-size: 13px;
font-weight: 300px;
line-height: 1.5;
text-align: left;
}

.checkbox.style-b {
display: inline-block;
position: relative;
padding-left: 30px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0;
padding-top: 4px;
}

.checkbox.style-b input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}

.checkbox.style-b input:checked ~ .checkbox__checkmark {
background-color: #fff;
}

.checkbox.style-b input:checked ~ .checkbox__checkmark:after {
opacity: 1;
}

.checkbox.style-b:hover input ~ .checkbox__checkmark {
background-color: #eee;
}

.checkbox.style-b:hover input:checked ~ .checkbox__checkmark {
background-color: #fff;
}

.checkbox.style-b .checkbox__checkmark {
position: absolute;
top: 3px;
left: 0;
height: 20px;
width: 20px;
background-color: #fff;
border: 1px solid #8F8F8F;
transition: background-color 0.25s ease;
border-radius: 4px;
}

.checkbox.style-b .checkbox__checkmark:after {
content: "";
position: absolute;
left: 8px;
top: 3px;
width: 7px;
height: 12px;
border: solid #862B90;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
opacity: 0;
transition: opacity 0.25s ease;
}

.checkbox.style-b .checkbox__body {
color: #212529;
font-feature-settings: 'clig' off, 'liga' off;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
   
<div id="input">
<div class="filter-container" style="display: flex; flex-direction: column; align-items: flex-start; width: 20%;">
<p>CHECKBOX FILTERS</p>
<p class="filter-title">ID</p>
  <lable><input class="option1" type="checkbox" value="01">01</label>
  <lable><input class="option1" type="checkbox" value="02">02</label>
  <lable><input class="option1" type="checkbox" value="03">03</label>
  <lable><input class="option1" type="checkbox" value="04">04</label>
<p class="filter-title">Name</p>
  <lable><input class="option2" type="checkbox" value="Harry">Harry</label>
  <lable><input class="option2" type="checkbox" value="Ron">Ron</label>
  <lable><input class="option2" type="checkbox" value="Malfoy">Malfoy</label>
  <lable><input class="option2" type="checkbox" value="Hagrid">Hagrid</label>
<p class="filter-title">Month</p>
  <lable><input class="option3" type="checkbox" value="JAN">JAN</label>
  <lable><input class="option3" type="checkbox" value="FEB">FEB</label>
  <lable><input class="option3" type="checkbox" value="MAR">MAR</label>
  <lable><input class="option3" type="checkbox" value="APR">APR</label>
  <lable><input class="option3" type="checkbox" value="MAY">MAY</label>
  <lable><input class="option3" type="checkbox" value="JUN">JUN</label>
<p class="filter-title">House</p>
  <lable><input class="option4" type="checkbox" value="Gryffindor">Gryffindor</label>
  <lable><input class="option4" type="checkbox" value="Slytherin">Slytherin</label>
  <lable><input class="option4" type="checkbox" value="None">None</label>
 <p class="filter-title">Language</p>
  <lable><input class="option4" type="checkbox" value="English">English</label>
  <lable><input class="option4" type="checkbox" value="Spanish">Spanish</label>
  <lable><input class="option4" type="checkbox" value="French">French</label>
</div>              
<p>FILTER TABLE</p>
  
<table id="myTable" class="myTable">
  <thead>
    <tr class="header">
    <th style="width: 3%; text-align: left; padding: 12px;">ID</th>
    <th style="width: 37%; text-align: left; padding: 12px;">Name</th>
    <th style="width: 5%; text-align: center; padding: 12px;">JAN</th>
    <th style="width: 5%; text-align: center; padding: 12px;">FEB</th>
    <th style="width: 5%; text-align: center; padding: 12px;">MAR</th>
    <th style="width: 5%; text-align: center; padding: 12px;">APR</th>
    <th style="width: 5%; text-align: center; padding: 12px;">MAY</th>
    <th style="width: 5%; text-align: center; padding: 12px;">JUN</th>
   </thead>
   <tbody>
      <tr>
        <td class="check1">01</td>
        <td class="check2">Harry</td>
        <td class="check3"></td>
        <td class="check3">FEB</td>
        <td class="check3">MAR</td>
        <td class="check3">APR</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check4" style="display: none;">Griffindor</td>
        <td class="check5" style="display: none;">Spanish</td>
        <td class="check5" style="display: none;">French</td>
      </tr>
      <tr>
        <td class="check1">01</td>
        <td class="check2">Harry</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3">APR</td>
        <td class="check3">MAY</td>
        <td class="check3">JUN</td>
        <td class="check4" style="display: none;">Griffindor</td>
        <td class="check5" style="display: none;">Engligh</td>
        <td class="check5" style="display: none;">Spanish</td>
      </tr>
      <tr>
        <td class="check1">02</td>
        <td class="check2">Ron</td>
        <td class="check3">JAN</td>
        <td class="check3"></td>
        <td class="check3">MAR</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check4" style="display: none;">Spanish</td>
      </tr>
      <tr>
        <td class="check1">03</td>
        <td class="check2">Hagrid</td>
        <td class="check3">JAN</td>
        <td class="check3">FEB</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3">JUN</td>
        <td class="check4" style="display: none;">None</td>
        <td class="check5" style="display: none;">Engligh</td>
      </tr>
      <tr>
        <td class="check1">03</td>
        <td class="check2">Hagrid</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3">MAR</td>
        <td class="check3">APR</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check4" style="display: none;">None</td>
        <td class="check5" style="display: none;">Engligh</td>
      </tr>
      <tr>
        <td class="check1">04</td>
        <td class="check2">Malfoy</td>
        <td class="check3">JAN</td>
        <td class="check3">FEB</td>
        <td class="check3">MAR</td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check3"></td>
        <td class="check4" style="display: none;">Slytherin</td>
        <td class="check5" style="display: none;">Engligh</td>
        <td class="check5" style="display: none;">Spanish</td>
        <td class="check5" style="display: none;">French</td>
      </tr>
    </tbody>
</table>
</div>

2

Answers


  1. The problem with your current logic is that you only evaluate the columns against each input explicitly for the filter input that was just changed.

    1. Your rules only need to match one of the selected options, your code hides rows if they contain a cell that doesn’t match, so if JAN is selected, rows that include FEB and MAR as well as JAN would be filtered out.

    2. Current code for un-selecting filters doesn’t validate the other column conditions, so un-selecting logic because ambiguous.

    To properly handle multiple criteria that can be both set and un-set you should re-evaluate all criteria. Conceptually however you should be evaluating the rows to see if they match all of the criteria, rather then evaluating the individual cells and then selecting back to the row.

    In javascript we can directly access the rows collection on a table, so we can easily iterate the rows and then compare the values of cells. In this case we only need the row value to match 1 of the options in each set, it don’t try to match all of the selected options in each option set:

    function applyFilters() {
      let option1 = getFilter(`input[type='checkbox'].option1`);
      let option2 = getFilter(`input[type='checkbox'].option2`);
      let option3 = getFilter(`input[type='checkbox'].option3`);
      let option4 = getFilter(`input[type='checkbox'].option4`);
      
      let rows = Array.from(document.querySelector('table.myTable').rows);
      rows.forEach(row => {
        // skip the header row
        if (row.getAttribute('class') === 'header') return;
        
        let visible = checkRowFilter(row, '.check1', option1) 
          && checkRowFilter(row, '.check2', option2)
          && checkRowFilter(row, '.check3', option3)
          && checkRowFilter(row, '.check4', option4);
          
        if(visible) 
          row.style = "";
        else 
          row.style = "display:none";
      });
    
    }
    
    /** Get the selected options
     * @param {string} filterQuery - The query selector expression for the checkboxes to evaluate.
     * @returns {Array} of the values for the selected checkboxes
     */
    function getFilter(filterQuery) {
      let options = Array.from(document.querySelectorAll(filterQuery));
      return options.filter(option => option.checked == true)
                    .map(option => option.value.toLowerCase());
    }
    
    /** Evaluate the columns matching the filterCol against the options
     * @param {row} row - The table row to evaluate.
     * @param {string} filterQuery - The query selector expression for the columns to evaluate.
     * @param {Array} options - The array of string values that are valid.
     * @returns {boolean} True if one of the cells matches one of the options
     */
    function checkRowFilter(row, filterQuery, options) {
      if (options.length > 0){
            let cells = Array.from(row.querySelectorAll(filterQuery));
            //let values = cells.filter(c => c.innerHTML.trim().length > 0).length;
            let matches = cells.filter(c => options.includes(c.innerHTML.toLowerCase())).length;
            return matches > 0;
      }
      return true;
    }
    
    document.querySelectorAll('.option1')
      .forEach(input => input.addEventListener('input', applyFilters));
    document.querySelectorAll('.option2')
      .forEach(input => input.addEventListener('input', applyFilters));
    document.querySelectorAll('.option3')
      .forEach(input => input.addEventListener('input', applyFilters));
    document.querySelectorAll('.option4')
      .forEach(input => input.addEventListener('input', applyFilters));
    

    This is a very first principals approach, while it can be made to work, a more common design would involve filtering the data and then re-rendering the data. An example of this is out of scope for this post but worth learning in your journey to becoming a front end developer.

    Login or Signup to reply.
  2. I sligtly changed the HTML and redid the JavaScript as I saw that the logic didn’t work.

    Here’s my working example:

    function updateTable ()
    {
      const myBody = tableSection.querySelector('tbody');
      const myFilters = {};
      
      // Update the options...
      for ( const myInput of filterSection.querySelectorAll('input') )
      {
        const myFilter = myInput.dataset.filter;
        const myValue = myInput.value.toLowerCase();
        
        if ( ! Reflect.has(myFilters, myFilter) )
        {
          myFilters[myFilter] = [];
        }
        
        if ( myInput.checked && ! myFilters[myFilter].includes(myValue) )
        {
          myFilters[myFilter].push(myValue);
        }
      }
      
      // Remove unused filters.
      for ( const myFilter in myFilters )
      {
        if ( myFilters[myFilter]?.length === 0 )
        {
          delete myFilters[myFilter];
        }
      }
      
      // Update the table...
      for ( const myRow of myBody.querySelectorAll('tr') )
      {
        let show = false;
        let currentMatches = 0;
        const requiredMatches = Object.keys(myFilters).length;
        
        filtering:
        for ( const myFilter in myFilters )
        {
          for ( const myElement of myRow.querySelectorAll('[data-filter=' + myFilter +']') )
          {
            if ( myFilters[myFilter].includes(myElement.innerHTML.toLowerCase()) )
            {
              currentMatches += 1;
              continue filtering;
            }
          }
        }
        
        if ( requiredMatches && ( currentMatches == requiredMatches ) )
        {
          show = true
        }
        
        myRow.style.display = show ? 'table-row' : 'none';
      }
    }
    
    // Add event to every checkbox.
    filterSection.querySelectorAll('[type=checkbox]').forEach
    (
      myInput => myInput.addEventListener
      (
        'change', changeEvent => updateTable()
      )
    );
    
    // Initial table update.
    console.clear();
    updateTable();
    #myTable {
    border-collapse: collapse;
    width: 100%; 
    border: 1px solid #ddd;
    font-size: 14px;
    margin: 5px;
    }
      
    #myTable th, #myTable td {
    text-align: left;
    padding: 12px;
    }
      
    #myTable tr {
    border-bottom: 1px solid #ddd;
    color: #58585B;
    font-family: "museo-sans-rounded";
    font-size: 14px;
    font-weight: 300px;
    }
      
    #myTable tr.header {
    background-color: #862B90;
    background-size: contain;
    color: white;
    font-family: "museo-sans-rounded";
    font-size: 16px;
    font-weight: 300px;
    }
    
    #myTable tr:hover {
    font-family: "museo-sans-rounded";
    font-size: 16px;
    font-weight: 300px;
    }
    
    #myTable td.month-pill {
    align-self: center;
    background-color: #FFC52E;
    border: none;
    color: #FFC52E;
    padding: 10px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    margin: 4px;
    border-radius: 16px;
    }
    
    #myTable td.month {
    color: #FFC52E;
    align-items: center;
    }
    
    .filter-container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 20%;
    }
    
    .filter-title {
    font-family: "museo-sans-rounded";
    color: #58585B !important;
    font-size: 14px;
    font-weight: 500px;
    line-height: 1.25;
    text-align: left;
    }
    
    .filter-option {
    font-family: "museo-sans-rounded";
    color: #58585B !important;
    font-size: 13px;
    font-weight: 300px;
    line-height: 1.5;
    text-align: left;
    }
    
    .checkbox.style-b {
    display: inline-block;
    position: relative;
    padding-left: 30px;
    cursor: pointer;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    margin: 0;
    padding-top: 4px;
    }
    
    .checkbox.style-b input {
    position: absolute;
    opacity: 0;
    cursor: pointer;
    height: 0;
    width: 0;
    }
    
    .checkbox.style-b input:checked ~ .checkbox__checkmark {
    background-color: #fff;
    }
    
    .checkbox.style-b input:checked ~ .checkbox__checkmark:after {
    opacity: 1;
    }
    
    .checkbox.style-b:hover input ~ .checkbox__checkmark {
    background-color: #eee;
    }
    
    .checkbox.style-b:hover input:checked ~ .checkbox__checkmark {
    background-color: #fff;
    }
    
    .checkbox.style-b .checkbox__checkmark {
    position: absolute;
    top: 3px;
    left: 0;
    height: 20px;
    width: 20px;
    background-color: #fff;
    border: 1px solid #8F8F8F;
    transition: background-color 0.25s ease;
    border-radius: 4px;
    }
    
    .checkbox.style-b .checkbox__checkmark:after {
    content: "";
    position: absolute;
    left: 8px;
    top: 3px;
    width: 7px;
    height: 12px;
    border: solid #862B90;
    border-width: 0 3px 3px 0;
    transform: rotate(45deg);
    opacity: 0;
    transition: opacity 0.25s ease;
    }
    
    .checkbox.style-b .checkbox__body {
    color: #212529;
    font-feature-settings: 'clig' off, 'liga' off;
    font-size: 14px;
    font-style: normal;
    font-weight: 400;
    line-height: 20px;
    }
    <div id="filterSection">
      <div class="filter-container" style="display: flex; flex-direction: column; align-items: flex-start; width: 20%;">
        <p>CHECKBOX FILTERS</p>
        <p class="filter-title">ID</p>
          <label><input data-filter="check1" type="checkbox" value="01">01</label>
          <label><input data-filter="check1" type="checkbox" value="02">02</label>
          <label><input data-filter="check1" type="checkbox" value="03">03</label>
          <label><input data-filter="check1" type="checkbox" value="04">04</label>
        <p class="filter-title">Name</p>
          <label><input data-filter="check2" type="checkbox" value="Harry">Harry</label>
          <label><input data-filter="check2" type="checkbox" value="Ron">Ron</label>
          <label><input data-filter="check2" type="checkbox" value="Malfoy">Malfoy</label>
          <label><input data-filter="check2" type="checkbox" value="Hagrid">Hagrid</label>
        <p class="filter-title">Month</p>
          <label><input data-filter="check3" type="checkbox" value="JAN">JAN</label>
          <label><input data-filter="check3" type="checkbox" value="FEB">FEB</label>
          <label><input data-filter="check3" type="checkbox" value="MAR">MAR</label>
          <label><input data-filter="check3" type="checkbox" value="APR">APR</label>
          <label><input data-filter="check3" type="checkbox" value="MAY">MAY</label>
          <label><input data-filter="check3" type="checkbox" value="JUN">JUN</label>
        <p class="filter-title">House</p>
          <label><input data-filter="check4" type="checkbox" value="Gryffindor">Gryffindor</label>
          <label><input data-filter="check4" type="checkbox" value="Slytherin">Slytherin</label>
          <label><input data-filter="check4" type="checkbox" value="None">None</label>
         <p class="filter-title">Language</p>
          <label><input data-filter="check4" type="checkbox" value="English">English</label>
          <label><input data-filter="check4" type="checkbox" value="Spanish">Spanish</label>
          <label><input data-filter="check4" type="checkbox" value="French">French</label>
      </div>
    </div>
    
    <div id="tableSection">
      <p>FILTER TABLE</p>
    
      <table id="myTable" class="myTable">
        <thead>
          <tr class="header">
            <th style="width: 3%; text-align: left; padding: 12px;">ID</th>
            <th style="width: 37%; text-align: left; padding: 12px;">Name</th>
            <th style="width: 5%; text-align: center; padding: 12px;">JAN</th>
            <th style="width: 5%; text-align: center; padding: 12px;">FEB</th>
            <th style="width: 5%; text-align: center; padding: 12px;">MAR</th>
            <th style="width: 5%; text-align: center; padding: 12px;">APR</th>
            <th style="width: 5%; text-align: center; padding: 12px;">MAY</th>
            <th style="width: 5%; text-align: center; padding: 12px;">JUN</th>
          </tr>
         </thead>
         <tbody>
            <tr>
              <td data-filter="check1">01</td>
              <td data-filter="check2">Harry</td>
              <td data-filter="check3"></td>
              <td data-filter="check3">FEB</td>
              <td data-filter="check3">MAR</td>
              <td data-filter="check3">APR</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check4" style="display: none;">Griffindor</td>
              <td data-filter="check5" style="display: none;">Spanish</td>
              <td data-filter="check5" style="display: none;">French</td>
            </tr>
            <tr>
              <td data-filter="check1">01</td>
              <td data-filter="check2">Harry</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3">APR</td>
              <td data-filter="check3">MAY</td>
              <td data-filter="check3">JUN</td>
              <td data-filter="check4" style="display: none;">Griffindor</td>
              <td data-filter="check5" style="display: none;">Engligh</td>
              <td data-filter="check5" style="display: none;">Spanish</td>
            </tr>
            <tr>
              <td data-filter="check1">02</td>
              <td data-filter="check2">Ron</td>
              <td data-filter="check3">JAN</td>
              <td data-filter="check3"></td>
              <td data-filter="check3">MAR</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check4" style="display: none;">Spanish</td>
            </tr>
            <tr>
              <td data-filter="check1">03</td>
              <td data-filter="check2">Hagrid</td>
              <td data-filter="check3">JAN</td>
              <td data-filter="check3">FEB</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3">JUN</td>
              <td data-filter="check4" style="display: none;">None</td>
              <td data-filter="check5" style="display: none;">Engligh</td>
            </tr>
            <tr>
              <td data-filter="check1">03</td>
              <td data-filter="check2">Hagrid</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3">MAR</td>
              <td data-filter="check3">APR</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check4" style="display: none;">None</td>
              <td data-filter="check5" style="display: none;">Engligh</td>
            </tr>
            <tr>
              <td data-filter="check1">04</td>
              <td data-filter="check2">Malfoy</td>
              <td data-filter="check3">JAN</td>
              <td data-filter="check3">FEB</td>
              <td data-filter="check3">MAR</td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check3"></td>
              <td data-filter="check4" style="display: none;">Slytherin</td>
              <td data-filter="check5" style="display: none;">Engligh</td>
              <td data-filter="check5" style="display: none;">Spanish</td>
              <td data-filter="check5" style="display: none;">French</td>
            </tr>
          </tbody>
      </table>
    </div>

    EDIT NOTE: This code allows you to change your HTML table/filters without the need to adjust the JavaScript. You just need to place matching data-filter attributes at both the <input> and <td> that you want filtered.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search