Search code examples
javascriptfiltertags

How display only div with selected tag with Javascript?


I created a working multiple tag filter with four <input type="checkbox"> which each have a tag which are also in my div class (code below) but I have an issue when two checkboxes are selected.

None of my div contains all four id in their class name but they all appear on my screen when I select all checkboxes while none should appear.

Here is an example to illustrate my point :


<div class="filterDiv adobePhotoshop sketch"> Div1 </div>
<div class="filterDiv adobePhotoshop adobeIndesign"> Div2 </div>

When I check my "Adobe Photoshop" and "Sketch" checkboxes for instance, I have div1 and div2 appearing while I only want to make appear div1 (because div2 have adobePhotoshop too but NOT sketch).


Any idea of how can I manage this please?

Javascript :

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 = 'flex'
      )
    } else {
      toFilter.forEach(
        (el) => el.style.display = 'none'
      );

      let selector = checkedFilters.map(
        (f) => `.filterDiv.${f}`
      ).join(',');
      document.querySelectorAll(selector).forEach(
        (el) => el.style.display = 'flex'
      );
    };
};

filters = document.querySelectorAll('input[type=checkbox]');

filters.forEach(
  (f) => f.addEventListener('change', filterToggle)
);

HTML :

<div id="btn-container">
    <input type="checkbox" id="adobePhotoshop"> <label for="adobePhotoshop">Adobe Photoshop</label>
    <input type="checkbox" id="adobeIndesign"> <label for="adobeIndesign">Adobe Indesign</label>
    <input type="checkbox" id="adobeIllustrator"> <label for="adobeIllustrator">Adobe Illustrator</label>
    <input type="checkbox" id="sketch"> <label for="sketch">Sketch</label>
</div>

<div class="grid-container">
    <div class="filterDiv adobePhotoshop adobeIndesign adobeIllustrator">
    Project1
    </div>
    <div class="filterDiv adobeIndesign adobeIllustrator">
    Project2
    </div>
    <div class="filterDiv adobePhotoshop">
    Project3
    </div>
    <div class="filterDiv adobePhotoshop sketch">
    Project4
    </div>
</div>

CSS :

.grid-container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    grid-gap: 60px;
}

.filterDiv {
    display: flex;
    height: 100%;
    width: 100%;
}

Solution

  • let selector = checkedFilters.map(
      (f) => `.filterDiv.${f}`
    ).join(',');
    

    Let's assume the "adobePhotoshop" and "sketch" were checked. The code above should generate an array of class names selector:

    [".filterDiv.adobePhotoshop", ".filterDiv.sketch"]
    

    ... which is used to select elements using:

    document.querySelectorAll(selector).forEach(
      (el) => el.style.display = 'flex'
    );
    

    ... which is mimicking the OR condition, where it will select elements that have at least one of them (".filterDiv.adobePhotoshop" or ".filterDiv.sketch") in the class names.

    In order to make the filtering to be the AND condition, then the selector logic should be:

    let selector = `.filterDiv.${checkedFilters.join('.')}`;
    

    ... to generate class name selector that only select elements that have all of the conditions:

    .filterDiv.adobePohotoshop.sketch