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: '\002B';
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 >
<div >
<label >
<input type="checkbox" id="choice1-1" name="choice1" onchange="change()" rel="filt-blue">
<span ></span>
</label>
<span><b>blue</b></span>
</div>
</div>
<div >
<div >
<label >
<input type="checkbox" id="choice1-2" name="choice1" onchange="change()" rel="filt-red">
<span ></span>
</label>
<span><b>red</b></span>
</div>
</div>
<hr>
<div >
<div >
<label >
<input type="checkbox" id="choice1-1a" name="choice2" onchange="change()" rel="filt-long">
<span ></span>
</label>
<span><b>long</b></span>
</div>
</div>
<div >
<div >
<label >
<input type="checkbox" id="choice1-2a" name="choice2" onchange="change()" rel="filt-short">
<span ></span>
</label>
<span><b>short</b></span>
</div>
</div>
<div >
<div >
<h6 ><b><?php echo $fetch['title'];?></b></h6>
<center><img src="/images/products/<?php echo $fetch['Img'];?>" style="width:160px;margin:0 auto;"></center>
<div >
<small><?php echo $fetch['detail'];?></small>
<h6><b>Starting from - £<?php echo $fetch['SFprice'];?></b></h6>
</div>
</div>
<div >
<h6 ><b><?php echo $fetch['title'];?></b></h6>
<center><img src="/images/products/<?php echo $fetch['Img'];?>" style="width:160px;margin:0 auto;"></center>
<div >
<small><?php echo $fetch['detail'];?></small>
<h6><b>Starting from - £<?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>
CodePudding user response:
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 >
<p >Blue Jeans</p>
<p >Blue Cups</p>
<p >Red Shirt</p>
<p >Red Shirt</p>
<p >Long Red Shirt</p>
<p >Short Red Shirt</p>
<p >Long Blue Shirt</p>
<p >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>
CodePudding user response:
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 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.
Here's my approach:
- initially hide all cards;
- iterate over each card first (not
input
); - for each card, iterate over the checkboxes;
- if the checkbox 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;
- once we've checked all inputs, 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 >
<input type="checkbox" name="choice1" onchange="change()" rel="filt-blue">
<span><b>blue</b></span>
</div>
<div >
<input type="checkbox" name="choice1" onchange="change()" rel="filt-red">
<span><b>red</b></span>
</div>
<div >
<input type="checkbox" name="choice2" onchange="change()" rel="filt-long">
<span><b>long</b></span>
</div>
<div >
<input type="checkbox" name="choice2" onchange="change()" rel="filt-short">
<span><b>short</b></span>
</div>
<br><br>
<div >
<div >
<b>Blue Long</b>
</div>
<div >
<b>Blue Short</b>
</div>
</div>
As a side note, 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.