Hejhejhej123
Hejhejhej123

Reputation: 1195

How to filter divs with javascript?

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

Answers (2)

Hejhejhej123
Hejhejhej123

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

David Thomas
David Thomas

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>

JS Fiddle demo.

References:

Upvotes: 1

Related Questions