Reputation: 1195
I am trying to create a filter with javascript. What I am trying to accomplish is:
When a checkbox is clicked, each div with the chosen class (cars or animals) should show (display: block), other divs that do not contain it should hide (display: none).
I have come up with the below code, but it is not working as thought. Only after you check both checkboxes it works, but then classes containing both cars and animals is hidden. It should show if it contains one of them.
The idea is really simple, see if checkboxes is checked and then push them to an array. If it is in that array, change to "display: block", otherwise add "display: none". But as I said something is wrong.. any idea what might be the problem?
Javascript:
let anotherOne = (x) => {
let all = document.getElementsByClassName('filterDiv'); //all filterable divs
let checkBoxCars = document.getElementById('cars');
let checkBoxAnimals = document.getElementById('animals');
let elements = document.getElementsByClassName(x); //divs with selected class
//Empty arrays for pushing checked and unchecked boxes.
let isChecked = []
let unChecked = []
if (checkBoxCars.checked == true) {
isChecked.push('cars')
} else {
unChecked.push('cars')
}
if (checkBoxAnimals.checked == true) {
isChecked.push('animals')
} else {
unChecked.push('animals')
}
//if checkbox value is in checked array then
if (isChecked.indexOf(x) > -1) {
//In the array
for (i = 0; i < elements.length; i++) {
elements[i].style.display = "block";
}
} else {
//Not in the array
for (i = 0; i < elements.length; i++) {
elements[i].style.display = "none";
}
}
//If all checkboxes is unset, show all
if (isChecked.length == 0) {
for (i = 0; i < all.length; i++) {
all[i].style.display = "block";
}
}
}
HTML:
<body>
<div id="myBtnContainer">
<input type="checkbox" id="cars" onclick="anotherOne('cars')">
<label for="cars">cars</label>
<input type="checkbox" id="animals" onclick="anotherOne('animals')">
<label for="animals">animals</label>
</div>
<!-- The filterable elements. Note that some have multiple class names (this can be used if they belong to multiple categories) -->
<div class="container">
<div id="cars" class="filterDiv show cars">BMW</div>
<div class="filterDiv show cars animals">CarDog1</div>
<div class="filterDiv show cars">Volvo</div>
<div class="filterDiv show cars">Mustang</div>
<div class="filterDiv show animals">Cat</div>
<div class="filterDiv show animals">Dog</div>
<div class="filterDiv show cars animals">CarDog2</div>
<div class="filterDiv show animals">Cow</div>
</div>
</body>
Upvotes: 2
Views: 1622
Reputation: 1195
I managed to solve this myself by making it simpler than I first thought, just kept my two arrays, pushed the classes in two them based on if they is checked or not, and then just added display: none and display: block to them. Works great with multiple classes. I guess you sometimes overthink things..
Here is my solution:
let anotherOne = (x) => {
console.log(x)
let all = document.getElementsByClassName('filterDiv'); //all filterable divs
let checkBoxCars = document.getElementById('cars');
let checkBoxAnimals = document.getElementById('animals');
//remembers if checked
let isChecked = []
let unChecked = []
if (checkBoxCars.checked == true){isChecked.push('cars')}else{unChecked.push('cars')}
if (checkBoxAnimals.checked == true){isChecked.push('animals')}else{unChecked.push('animals')}
if (checkBoxPlant.checked == true){isChecked.push('plants')}else{unChecked.push('plants')}
console.log('checked: '+ isChecked + ' unchecked: ' + unChecked)
//Add display none to unChecked array
for(i=0;i<unChecked.length;i++) {
let getClass = document.getElementsByClassName(unChecked[i]);
for(c=0;c<getClass.length;c++) {
getClass[c].style.display ="none";
}}
//Add display block to isChecked array
for(i=0;i<isChecked.length;i++) {
let getClass = document.getElementsByClassName(isChecked[i]);
for(c=0;c<getClass.length;c++) {
getClass[c].style.display ="block";
}}
Upvotes: 1
Reputation: 253396
One approach is as follows, though do bear in mind that I removed the duplicate id
from the following element:
<div id="cars" class="filterDiv show cars">BMW</div>
Since that id
is also used – more appropriately – for the <input>
element, and a duplicate id
invalidates the document and causes problems with JavaScript.
That's the only change I've made to the HTML, the demo is below:
// use of a named function, composed using an Arrow function expression,
// as we don't need to refer to a 'this' within the function, here we
// also pass in the Event Object ('e') from the use of
// EventTarget.addEventListener() (later):
const filterToggle = (e) => {
// here use document.querySelectorAll() to find all <input>
// elements with a type-attribute with that value set to
// 'checkbox' which is also checked; we then use the spread
// operator to convert the iterable NodeList into an Array:
const checkedFilters = [
...document.querySelectorAll('input[type=checkbox]:checked')
// we then use Array.prototype.map():
].map(
// to create an Array of the checked check-boxes id properties:
(el) => el.id
),
// we then derive an Array, as above, of all elements matching
// the '.filterDiv' selector:
toFilter = [...document.querySelectorAll('.filterDiv')];
// if there are no checked filters we show all elements:
if (checkedFilters.length === 0) {
// iterating over the Array of nodes using
// Array.prototype.forEach() along with an anonymous
// Arrow function expression to update the display
// property to 'block':
toFilter.forEach(
(el) => el.style.display = 'block'
)
} else {
// in the event that some check-boxes were checked,
// we first iterate over the .filterDiv elements to
// hide them all:
toFilter.forEach(
(el) => el.style.display = 'none'
);
// then create a new Array from the checkedFilters
// Array using Array.prototype.map():
let selector = checkedFilters.map(
// here we use a template-literal to create a string of
// '.filterDiv.<filter-id>'
(f) => `.filterDiv.${f}`
// then we join the elements of the Array of selectors
// together, into a String, using Array.prototype.join()
// using a comma (',')
).join(',');
// here we use document.querySelectorAll() with the
// created selector, and iterate over that NodeList
// using NodeList.prototype.forEach() to update the
// display of all elements matching the selector in
// order to show them again:
document.querySelectorAll(selector).forEach(
(el) => el.style.display = 'block'
);
}
},
// here we find the check-boxes:
filters = document.querySelectorAll('input[type=checkbox]');
// we iterate over the check-boxes using NodeList.prototype.forEach():
filters.forEach(
// along with an anonymous Arrow function expression, to bind the
// filterToggle() (note the deliberate lack of parentheses) as the
// event-handler for the 'change' event:
(f) => f.addEventListener('change', filterToggle)
);
const filterToggle = (e) => {
const checkedFilters = [...document.querySelectorAll('input[type=checkbox]:checked')].map(
(el) => el.id
),
toFilter = [...document.querySelectorAll('.filterDiv')];
if (checkedFilters.length === 0) {
toFilter.forEach(
(el) => el.style.display = 'block'
)
} else {
toFilter.forEach(
(el) => el.style.display = 'none'
);
let selector = checkedFilters.map(
(f) => `.filterDiv.${f}`
).join(',');
document.querySelectorAll(selector).forEach(
(el) => el.style.display = 'block'
);
}
},
filters = document.querySelectorAll('input[type=checkbox]');
filters.forEach(
(f) => f.addEventListener('change', filterToggle)
);
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-template-rows: 4em;
grid-auto-rows: 4em;
grid-gap: 0.5em;
}
.filterDiv {
border: 2px solid palegreen;
}
<div id="myBtnContainer">
<input type="checkbox" id="cars">
<label for="cars">cars</label>
<input type="checkbox" id="animals">
<label for="animals">animals</label>
</div>
<!-- The filterable elements. Note that some have multiple class names (this can be used if they belong to multiple categories) -->
<div class="container">
<div class="filterDiv show cars">BMW</div>
<div class="filterDiv show cars animals">CarDog1</div>
<div class="filterDiv show cars">Volvo</div>
<div class="filterDiv show cars">Mustang</div>
<div class="filterDiv show animals">Cat</div>
<div class="filterDiv show animals">Dog</div>
<div class="filterDiv show cars animals">CarDog2</div>
<div class="filterDiv show animals">Cow</div>
</div>
References:
Array.prototype.filter()
.Array.prototype.forEach()
.Array.prototype.map()
.document.querySelector()
.document.querySelectorAll()
.NodeList.prototype.forEach()
.Template-literal strings
.Upvotes: 1