javascripthtmljquerycssjquery-isotope

How do you combine multiple filters in isotope JS?


I have two content type areas which contain unique filter options. These are:

  1. type
  2. tag

I'm trying to utilise isotope.js, to achieve a dual filtering layout, but it always gives the last clicked filter priority.

See use case here (reference below demo):

  1. If I click "Blog & News", I should see two posts (works)
  2. If I then also click "Case studies", I should see both case studies and blog posts. Any item that is checked should show in the listing.

The filters in combination isn't working.

The documentation says the arrange() method can handle multiple filter instances, but it isn't working in my use case.

I've also tried using the concatValues() function to concatenate the values (as shown in many demos), but it still doesn't yield the correct results.

See interactive demo here.

document.addEventListener('DOMContentLoaded', function() {

  var container = document.querySelector('.grid');
  var gridItems = container.querySelectorAll('.grid-item');
  const optionLinks = document.querySelectorAll('.rSidebar__options-li');

  var iso = new Isotope(container, {
    itemSelector: '.resourceCard',
    layoutMode: 'fitRows',
    transitionDuration: '0.5s',
  });

  var filters = {};

  function concatValues( obj ) {
    var value = '';
    for ( var prop in obj ) {
      value += obj[ prop ];
    }
    return value;
  }

  function handleFilterClick(event, filters, iso) {
    var listItem = event.target;
    var filterGroup = listItem.closest('.rSidebar__options').getAttribute('data-filter-group');
    var filterValue = listItem.getAttribute('data-filter');

    if (filters[filterGroup] === filterValue) {
      delete filters[filterGroup];
    } else {
      filters[filterGroup] = filterValue;
    }

    // Combine filters
    var filterValues = Object.values(filters).join(', ');
    // var filterValues = concatValues( filters );


    // debugging
    console.log('List Item:', listItem);
    console.log('Filter Group:', filterGroup);
    console.log('Filter Value:', filterValue);
    console.log('Filters Object:', filters);
    console.log('Filter Values:', filterValues);

    iso.arrange({ filter: filterValues });
  }

  optionLinks.forEach(function(optionLink) {
    optionLink.addEventListener('click', function(event) {
      event.preventDefault();
      this.classList.toggle('selected');
      handleFilterClick(event, filters, iso);
    });
  });


});
.post {
  padding: 100px;
}

.rSidebar__box {
  margin-bottom: 30px;
}
.rSidebar__options {
  padding-left: 0;
}
.rSidebar__options-li {
  margin-bottom: 17px;
  display: flex;
  align-items: center;
  cursor: pointer;
  width: fit-content;
}
.rSidebar__options-li.selected .rSidebar__options-square {
  background-color: #185A7D;
}
.rSidebar__options-square {
  height: 20px;
  width: 20px;
  transition: all 0.5s ease;
  border: 2px solid #000000;
}
.rSidebar__options-label {
  margin-left: 10px;
}

.grid {
  display: flex;
  flex-wrap: wrap;
  margin: -14px 0 0 -14px;
}
.grid-item {
  box-sizing: border-box;
  width: calc(33.33% - 14px);
  margin: 14px 0 18px 14px;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>


<div class="post">
  <div class="container">
    <div class="row justify-content-between">

      <!-- SIDEBAR -->
      <div class="col-3">
        <div class="rSidebar">

          <!-- tags -->
          <div class="rSidebar__box">
            <span class="rSidebar__label d-block fw-bold">Filter by tag</span>
            <ul class="rSidebar__options button-group" data-filter-group="type">
              <li class="rSidebar__options-li" data-filter=".pdf">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block buttonTemp" data-filter=".pdf">PDF</span>
              </li>
                <li class="rSidebar__options-li" data-filter=".article">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block buttonTemp" data-filter=".article">Article</span>
              </li>
            </ul>
          </div>

          <!--  type -->
          <div class="rSidebar__box">
            <span class="rSidebar__label d-block fw-bold">Filter by type</span>
            <ul class="rSidebar__options button-group" data-filter-group="type">
              <li class="rSidebar__options-li" data-filter=".blogs-and-news">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block buttonTemp" data-filter=".blogs-and-news">Blog & News</span>
              </li>
                <li class="rSidebar__options-li" data-filter=".case-study">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block buttonTemp" data-filter=".case-study">Case Studies</span>
              </li>
            </ul>
          </div>
          <!-- end -->
        </div>
      </div>
      <!-- END -->

      <!-- GRID -->
      <div class="col-7">
        <div class="grid">
          <article class="resourceCard grid-item case-study pdf"><span class="resourceCard__body-title">Case study, PDF post</span></article>
          <article class="resourceCard grid-item blogs-and-news"><span class="resourceCard__body-title">Blogs and news post</span></article>
          <article class="resourceCard grid-item blogs-and-news article"><span class="resourceCard__body-title">Blogs and news, article post</span></article>
        </div>
      </div>
      <!-- END -->

    </div>
  </div>
</div>

Latest attempt

document.addEventListener('DOMContentLoaded', function() {

  var container = document.querySelector('.grid');
  var gridItems = container.querySelectorAll('.grid-item');
  const optionLinks = document.querySelectorAll('.rSidebar__options-li');

  var iso = new Isotope(container, {
    itemSelector: '.resourceCard',
    layoutMode: 'fitRows',
    transitionDuration: '0.5s',
  });

  var filters = {};

  function concatValues( obj ) {
    var value = '';
    for ( var prop in obj ) {
      value += obj[ prop ];
    }
    return value;
  }

  function handleFilterClick(event, filters, iso) {
    var listItem = event.target;
    var filterGroup = listItem.closest('.rSidebar__options').getAttribute('data-filter-group');
    var filterValue = listItem.getAttribute('data-filter');

    var allowMultiple = listItem.closest('.rSidebar__options').getAttribute('data-multiple') === 'true';

    if (allowMultiple) {
      // toggle the filter value
      if (filters[filterGroup] && filters[filterGroup].includes(filterValue)) {
        // remove the filter value if it already exists
        filters[filterGroup] = filters[filterGroup].filter(value => value !== filterValue);
      } else {
        // add the filter value if it doesn't exist
        if (!filters[filterGroup]) {
          filters[filterGroup] = [];
        }
        filters[filterGroup].push(filterValue);
      }
    } else {
      // replace the filter value
      filters[filterGroup] = [filterValue];
    }

    var filterValues = concatValues( filters );

    // console.log('List Item:', listItem);
    // console.log('Filter Group:', filterGroup);
    // console.log('Filter Value:', filterValue);
    // console.log('Filters Object:', filters);
    // console.log('Filter Values:', filterValues);

    iso.arrange({ filter: filterValues });

  }


  optionLinks.forEach(function(optionLink) {
    optionLink.addEventListener('click', function(event) {
      event.preventDefault();
      this.classList.toggle('selected');
      handleFilterClick(event, filters, iso);
    });
  });


});


Solution

  • You need to update the object value which is created whenever the tag/type is clicked with value selected and then use that updated array to pass in filter.

    In below code I have added condition i.e : if there is selected class on the target element then only add/update the value of array (filters) else just remove that data-filter value from the key.

    Demo Code :

    var filters = {};
    var container = document.querySelector('.grid');
    var gridItems = container.querySelectorAll('.grid-item');
    const optionLinks = document.querySelectorAll('.rSidebar__options-li');
    
    var iso = new Isotope(container, {
      itemSelector: '.resourceCard',
      layoutMode: 'fitRows',
      transitionDuration: '0.5s',
    });
    
    function concatValues(obj) {
      var value = [];
      for (var prop in obj) {
        value.push(obj[prop]);
      }
    
      return value.flat().join(', ');
    }
    
    function handleFilterClick(event, filters) {
      console.clear()
      var listItem = event;
      var filterGroup = listItem.closest('.rSidebar__options').getAttribute('data-filter-group');
      var data_filter = listItem.getAttribute('data-filter');
    
      //if selected class present do below : 
      if (listItem.classList.contains('selected')) {
        if (!filters[filterGroup]) {
          filters[filterGroup] = []
        }
        filters[filterGroup].push(data_filter)
      } else {
        filters[filterGroup] = filters[filterGroup].filter(data => data !== data_filter)
      }
      var filterValues = concatValues(filters);
      console.log(filterValues)
    
      iso.arrange({
        filter: filterValues
      });
    }
    
    optionLinks.forEach(function(optionLink) {
      optionLink.addEventListener('click', function(event) {
        event.preventDefault();
        this.classList.toggle('selected');
        handleFilterClick(this, filters);
      });
    });
    .post {
      padding: 100px;
    }
    
    .rSidebar__box {
      margin-bottom: 30px;
    }
    
    .rSidebar__options {
      padding-left: 0;
    }
    
    .rSidebar__options-li {
      margin-bottom: 17px;
      display: flex;
      align-items: center;
      cursor: pointer;
      width: fit-content;
    }
    
    .rSidebar__options-li.selected .rSidebar__options-square {
      background-color: #185A7D;
    }
    
    .rSidebar__options-square {
      height: 20px;
      width: 20px;
      transition: all 0.5s ease;
      border: 2px solid #000000;
    }
    
    .rSidebar__options-label {
      margin-left: 10px;
    }
    
    .grid {
      display: flex;
      flex-wrap: wrap;
      margin: -14px 0 0 -14px;
    }
    
    .grid-item {
      box-sizing: border-box;
      width: calc(45% - 14px);
      margin: 14px 0 18px 14px;
      border: 2px solid;
      padding: 20px;
    }
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
    
    
    <div class="post">
      <div class="container">
        <div class="row justify-content-between">
    
          <!-- SIDEBAR -->
          <div class="col-3">
            <div class="rSidebar">
    
              <!-- tags -->
              <div class="rSidebar__box">
                <span class="rSidebar__label d-block fw-bold">Filter by tag</span>
                <ul class="rSidebar__options button-group" data-filter-group="tag">
                  <li class="rSidebar__options-li" data-filter=".pdf">
                    <span class="rSidebar__options-square"></span>
                    <span class="rSidebar__options-label d-block buttonTemp" data-filter=".pdf">PDF</span>
                  </li>
                  <li class="rSidebar__options-li" data-filter=".article">
                    <span class="rSidebar__options-square"></span>
                    <span class="rSidebar__options-label d-block buttonTemp" data-filter=".article">Article</span>
                  </li>
                </ul>
              </div>
    
              <!--  type -->
              <div class="rSidebar__box">
                <span class="rSidebar__label d-block fw-bold">Filter by type</span>
                <ul class="rSidebar__options button-group" data-filter-group="type">
                  <li class="rSidebar__options-li" data-filter=".blogs-and-news">
                    <span class="rSidebar__options-square"></span>
                    <span class="rSidebar__options-label d-block buttonTemp" data-filter=".blogs-and-news">Blog & News</span>
                  </li>
                  <li class="rSidebar__options-li" data-filter=".case-study">
                    <span class="rSidebar__options-square"></span>
                    <span class="rSidebar__options-label d-block buttonTemp" data-filter=".case-study">Case Studies</span>
                  </li>
                </ul>
              </div>
              <!-- end -->
            </div>
          </div>
          <!-- END -->
    
          <!-- GRID -->
          <div class="col-7">
            <div class="grid">
              <article class="resourceCard grid-item case-study pdf"><span class="resourceCard__body-title">Case study, PDF post</span></article>
              <article class="resourceCard grid-item blogs-and-news"><span class="resourceCard__body-title">Blogs and news post</span></article>
              <article class="resourceCard grid-item blogs-and-news article"><span class="resourceCard__body-title">Blogs and news, article post</span></article>
              <article class="resourceCard grid-item blogs-and-news article pdf case-study"><span class="resourceCard__body-title">Blogs and news,article,pdf,case study </span></article>
            </div>
          </div>
          <!-- END -->
    
        </div>
      </div>
    </div>