skip to Main Content

I have a product page that fetches the products from a database table, I am using foreach to pull each product & each product displays as a card and using classes to filter them.

The problem is that the checkbox filter isnt working properly with multiple values (classes), if I have 2 classes the filter is only detecting the 1st class rather than all the classes(filters).

So for example, if I check the "Blue" checkbox I wont see any results since the JS is searching for a div that has only that one class ("filt-blue") but I need it to display all divs that contain "filt-blue" as well as any other filters that may be added.

See more on JSFIDDLE

function change() {
  var checkboxes = document.getElementsByClassName('checkbox');
  var chekboxInputs = Array.from(checkboxes).map(a => a.querySelector('input'));
  var allAreUnselected = chekboxInputs.every(function(elem) {
    return !elem.checked;
  });
  if (allAreUnselected) {
    chekboxInputs.forEach(function(input) {
      Array.from(document.querySelectorAll("." + input.getAttribute("rel"))).forEach(function(item) {
        item.style.display = 'block';
      });
    });
  } else {
    chekboxInputs.forEach(function(input) {
      Array.from(document.querySelectorAll("." + input.getAttribute("rel"))).forEach(function(item) {
        item.style.display = input.checked ? 'block' : 'none';
      });
    });
  }
}
change();
  @media(max-width:768px){
   .card{
    width:100% !important;
    margin:12px 6px !important;
    }
  }
  .card {
  text-align:left;
  border-radius:4px;
  margin:24px;
  width:320px;
  min-height:340px;
  transition: all 0.2s;
  border:var(--image-select-border);
}
.cards {
  display: flex;
  flex-wrap: wrap;
  justify-content:center;
}
.card-body {
  flex: 1 1 auto;
  padding:12px;
}
.card-title {
  margin-bottom:16px;
  padding:12px;
}
@media(max-width:768px){
.card-img-top{
  width:240px !important;
}
}
@media(max-width:768px){
.filtbtn{
  width:90% !important;
  margin-left:5% !important;
  float:none !important;
}
}
.filterDiv {
  display: none;
}
.show {
  display: block;
}
@media(max-width:768px){
#opts{
  margin-left:5% !important;
}
}
.optsel{
  border-bottom:2px solid #0d6efd;
  border-top: none;
  border-left: none;
  border-right: none;
}
.sidebar {
  height:100%;
  width:0;
  position:fixed;
  z-index:1;
  top:76px;
  right:0;
  background-color:#111;
  overflow-x:hidden;
  transition: 0.5s;
  padding-top: 60px;
}
.sidebar a {
  padding:8px 12px;
  text-decoration: none;
  font-size:16px;
  color: #818181;
  display: block;
  transition: 0.3s;
}
.sidebar .closebtn {
  position: absolute;
  top: 0;
  margin-right:12px;
  font-size:18px;
  color:#222 !important;
  background-color:#fff;
  width:100%;
}
@media(max-width:768px){
  .closebtn{
    top:4px !important;
  }
}
.openbtn {
  font-size: 20px;
  cursor: pointer;
  background-color: #111;
  color: white;
  padding: 10px 15px;
  border: none;
}
.openbtn:hover {
  background-color: #444;
}
#main {
  transition: margin-right .5s; /* If you want a transition effect */
  padding: 20px;
}
/* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
@media screen and (max-height: 450px) {
  .sidebar {padding-top: 15px;}
  .sidebar a {font-size: 22px;}
}
.optbtn{
  background-color:#fff;
  color: #222;
  cursor: pointer;
  padding:14px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 16px;
  transition: 0.4s;
}
.accordion {
  background-color:#111;
  color: #fff !important;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
  transition: 0.4s;
}
.notaccordion {
  background-color:#111;
  color: #fff !important;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: center;
  outline: none;
  font-size:18px;
  transition: 0.4s;
  font-weight:bolder;
}
.active, .accordion:hover {
  opacity:0.9;
}
.accordion:after {
  content: '02B';
  color: #777;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}
.active:after {
  content: "2212";
}
.filterpanel {
  padding:0 18px;
  background-color:#fff;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
}
.checkbox{
  color:#222 !important;
  padding:12px 12px;
}
.checkbox-button {
  cursor: pointer;
}
.checkbox span{
  margin-left:12px;
}
.checkbox input[type=checkbox] {
    box-sizing: border-box;
    padding: 0;
}
.checkbox input {
    font-size: 1rem;
    line-height: 1.5;
    padding: 11px 23px;
    border: 1px solid black;
    border-radius: 0;
    outline: 0;
    background-color: transparent;
}
.checkbox-button__input {
    opacity: 0;
    position: absolute;
}
.checkbox-button__control {
    position: relative;
    display: inline-block;
    width: 20px;
    height: 20px;
    vertical-align: middle;
    background-color: inherit;
    color: #017b5f;
    border: 2px solid #666;
}
.checkbox-button__input:checked+.checkbox-button__control:after {
    content: "";
    display: block;
    position: absolute;
    top: 2px;
    left: 2px;
    width: 12px;
    height: 12px;
    background-color:#0d6efd;
}
.checkbox-button__input:checked+.checkbox-button__control {
    border-color:black;
    border:2px solid black;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" id="bootstrap-css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">




<div class="filter">
  <div class="checkbox">
    <label class="checkbox-button">
      <input type="checkbox" class="checkbox-button__input" id="choice1-1" name="choice1" onchange="change()" rel="filt-blue">
      <span class="checkbox-button__control"></span>
    </label>
    <span><b>blue</b></span>
  </div>
</div>
<div class="filter">
  <div class="checkbox">
    <label class="checkbox-button">
      <input type="checkbox" class="checkbox-button__input" id="choice1-2" name="choice1" onchange="change()" rel="filt-red">
      <span class="checkbox-button__control"></span>
    </label>
    <span><b>red</b></span>
  </div>
</div>

<hr>

<div class="filter">
  <div class="checkbox">
    <label class="checkbox-button">
      <input type="checkbox" class="checkbox-button__input" id="choice1-1a" name="choice2" onchange="change()" rel="filt-long">
      <span class="checkbox-button__control"></span>
    </label>
    <span><b>long</b></span>
  </div>
</div>
<div class="filter">
  <div class="checkbox">
    <label class="checkbox-button">
      <input type="checkbox" class="checkbox-button__input" id="choice1-2a" name="choice2" onchange="change()" rel="filt-short">
      <span class="checkbox-button__control"></span>
    </label>
    <span><b>short</b></span>
  </div>
</div>




<div class="cards">

  <div class="card filt-blue filt-long">
    <h6 class="card-title"><b><?php echo $fetch['title'];?></b></h6>
    <center><img src="/images/products/<?php echo $fetch['Img'];?>" class="card-img-top" style="width:160px;margin:0 auto;"></center>
    <div class="card-body">
      <small><?php echo $fetch['detail'];?></small>
      <h6><b>Starting from - &pound;<?php echo $fetch['SFprice'];?></b></h6>
    </div>
  </div>

<div class="card filt-blue filt-short">
    <h6 class="card-title"><b><?php echo $fetch['title'];?></b></h6>
    <center><img src="/images/products/<?php echo $fetch['Img'];?>" class="card-img-top" style="width:160px;margin:0 auto;"></center>
    <div class="card-body">
      <small><?php echo $fetch['detail'];?></small>
      <h6><b>Starting from - &pound;<?php echo $fetch['SFprice'];?></b></h6>
    </div>
  </div>
  
</div>




<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>

2

Answers


  1. This code should help your filter take into account multiple classes. There’s a couple of ways you can filter. For example, if you have "long and blue" do you want to show ONLY long and blue? Or would you want to show a "Red Long" as well. This particular code takes into account multiple filter classes but in a logical OR fashion instead of an AND logic.

    <body>
    
    <label>Blue</label>
    <input type="checkbox" name="filter-blue" onchange="change()">
    
    <label>Red</label>
    <input type="checkbox" name="filter-red" onchange="change()">
    
    <label>Long</label>
    <input type="checkbox" name="filter-long" onchange="change()">
    
    <label>Short</label>
    <input type="checkbox" name="filter-short" onchange="change()">
    
    <div class="cards">
        
        <p class="filter-blue">Blue Jeans</p>
        <p class="filter-blue">Blue Cups</p>
        <p class="filter-red">Red Shirt</p>
        <p class="filter-red">Red Shirt</p>
        <p class="filter-red filter-long">Long Red Shirt</p>
        <p class="filter-red filter-short">Short Red Shirt</p>
        <p class="filter-blue filter-long">Long Blue Shirt</p>
        <p class="filter-blue filter-short">Short Blue Shirt</p>
    
    </div>
    
    
    <script type="text/javascript">
        
    function change() {
    
        // Step 1 - Get checked filters
    
        let checkboxes = document.querySelectorAll('input[type="checkbox"]'),
            filtered = [];
    
        checkboxes.forEach(checkbox => {
            if (checkbox.checked) {
                filtered.push(checkbox.name);
            }
        });
    
        // Step 2 - Show cards based on the filters
        let cards = document.querySelectorAll('.cards p');
    
        cards.forEach(card => {
            if (
                filtered.length === 0 || // If no filter is checked then show everything
                filtered.some(r => card.classList.contains(r)) // If the filter matches the cards class. Filter it.
            ) {
                card.style.display = 'block';
            } else {
                card.style.display = 'none';
            }
        });
    }
    
    </script>
    
    </body>
    
    Login or Signup to reply.
  2. Why doesn’t the current code work?

    Your code iterates over every input, finding all cards matching that input. The first input is blue, and the code iterates over all cards and correctly displays those matching blue. But it then continues to the next iteration, the red checkbox. No cards match and so they are both immediately hidden. So any matching card(s) are actually shown and then immediately hidden.

    OK, how do we fix it?

    Here’s my approach:

    • Since we need to evaluate all checkboxes for each indvidual card, flip the order of iteration – for each card, iterate over each checkbox (rather than the other way around as you have);
    • Evaluate each checkbox: if it is checked and the current card has that attribute, flag it as matching, and continue checking the other checkboxes;
    • But if the checkbox is checked and the current card does not have that attribute, flag it as a non-match. Now we must immeidately bail out, so a future match doesn’t override this failure;
    • After we’ve checked all inputs (or bailed out on a failure), display or hide the card depending on the final state of our match;
    function change() {
        var checkboxes = document.getElementsByClassName('checkbox');
        var chekboxInputs = Array.from(checkboxes).map(a => a.querySelector('input'));
        var allAreUnselected = chekboxInputs.every(function(elem) {
            return !elem.checked;
        });
    
        if (allAreUnselected) {
            chekboxInputs.forEach(function(input) {
                Array.from(document.querySelectorAll("." + input.getAttribute("rel"))).forEach(function(item) {
                    item.style.display = 'block';
                });
            });
    
        } else {
            Array.from(document.querySelectorAll(".card")).forEach(function(card) {
                // console.log('Start card: ', card);
                let match = false;
    
                for (const input of chekboxInputs) {
                    let attribute = input.getAttribute("rel");
                    // console.log('processing input with rel:', attribute);
    
                    if (input.checked && card.classList.contains(attribute)) {
                        // console.log('input is checked and card matches');
                        match = true;
                        
                    } else if (input.checked && ! card.classList.contains(attribute)) {
                        // console.log('input is checked and card does not match');
                        match = false;
                        break;
                    }
                }
    
                // console.log('done checking inputs, match is:', match);
                card.style.display = match ? 'block' : 'none';
            });
        }
    }
    change();
    .card {
      border: 1px solid black;
    }
    <div class="checkbox">
        <input type="checkbox" name="choice1" onchange="change()" rel="filt-blue">
        <span><b>blue</b></span>
    </div>
    
    <div class="checkbox">
        <input type="checkbox" name="choice1" onchange="change()" rel="filt-red">
        <span><b>red</b></span>
    </div>
    
    <div class="checkbox">
        <input type="checkbox" name="choice2" onchange="change()" rel="filt-long">
        <span><b>long</b></span>
    </div>
    
    <div class="checkbox">
        <input type="checkbox" name="choice2" onchange="change()" rel="filt-short">
        <span><b>short</b></span>
    </div>
    
    <br><br>
    
    <div class="cards">
        <div class="card filt-blue filt-long">
            <b>Blue Long</b>
        </div>
    
        <div class="card filt-blue filt-short">
            <b>Blue Short</b>
        </div>
    </div>

    Notes

    • It is generally considered good practice to separate your HTML and JS. Here that would mean removing the inline onchange="change()" in the HTML, and replacing them with an event handler in the JS:

      let checkbox = document.querySelector("input[name=checkbox]");
      checkbox.addEventListener('change', change);
      
    • You will make it so much easier for others to help if you can create a minimal, complete, and verifiable example of the problem. In this case a good chunk of the time I spent on this problem was evaulating what I could strip out – a lot of the HTML and most of the CSS classes are irrlevant. The PHP obviously does not work, and none of the CSS matters. Neither Bootstrap nor jQuery CSS or JS refs are necessary.
      When you strip out everything not related to the problem it is that much easier to understand, debug, and work with.

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