Thursday, 5 October 2023

Using isotope.js to define an AND relationship when filtering using multiple options

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.

The way I want it to function is:

  1. If only one tag is selected, then show results with that tag
  2. If more than one tag is selected, then show results that have any of those tags (both do not need to exist together on a card)
  3. If a type is selected, only show results that fall under that type
  4. If one type is selected and one tag, show results for posts where the tag exists on the selected type
  5. If one type is selected and multiple tags, then show posts that fall under that type that have either of those tags.
  6. If more than one type is selected and one or more tags, then show posts where the tags exist on either type

For example, using my demo below, here are some use cases:

  1. If I click on "Video Demos & Tours", I should see two video posts (card 1 and 3) - WORKS
  2. If I click on "Video Demos & Tours" and then "Ansible", I should see only see card 3 - DOESN'T WORK
  3. If I click on "Blog & News", I should see 3 cards (card 2, 4, 5) - WORKS
  4. If I click on "Blog & News" and then "Ansible", I should see cards 4 and 5
  5. If I click on "Blog & News", "Ansible" and then "Automation", I should see cards 2,4 and 5

However, in my current demo, though the console log seems to be on the right lines, it doesn't perform the way I intent it to.

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.push(obj[prop]);
    }
    return value.flat().join(", ");
  }

  function handleFilterClick(event, filters) {
    var listItem = event;
    var filterGroup = listItem
      .closest(".rSidebar__options")
      .getAttribute("data-filter-group");
    var data_filter = listItem.getAttribute("data-filter");

    if (filterGroup === "type") {
      filters[filterGroup] = [data_filter];
    } else {
      if (!filters[filterGroup]) {
        filters[filterGroup] = [];
      }

      if (listItem.classList.contains("selected")) {
        filters[filterGroup].push(data_filter);
      } else {
        filters[filterGroup] = filters[filterGroup].filter(
          (tag) => tag !== data_filter
        );
      }
    }

    // Combine the type filter with the selected tag filters using an AND relationship
    var filterValues = [];

    // Handle type filter
    if (filters["type"]) {
      filterValues.push("." + filters["type"][0]);
    }

    // Handle tags filter if it's defined
    if (filters["tag"]) {
      var selectedType = filters["type"][0];
      filters["tag"].forEach((tag) => {
        filterValues.push("." + selectedType + "." + tag);
      });
    }

    var finalFilter = filterValues.join(", ");
    console.log(finalFilter);

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

  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 black;
  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="ansible">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block" data-filter="ansible">Ansible</span>
              </li>
                <li class="rSidebar__options-li" data-filter="automation">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block" data-filter="automation">Automation</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" data-filter="blogs-and-news">Blog & News</span>
              </li>
              <li class="rSidebar__options-li" data-filter="video-demos-tour">
                <span class="rSidebar__options-square"></span>
                <span class="rSidebar__options-label d-block" data-filter="video-demos-tours">Video Demos & Tours</span>
              </li>
            </ul>
          </div>
          <!-- end -->
        </div>
      </div>
      <!-- END -->

      <!-- GRID -->
      <div class="col-7">
        <div class="grid">
          <article class="resourceCard grid-item video-demos-tour automation"><span class="resourceCard__body-title">Card 1<br>Type: Video Demo & Tour<br>Tag: Automation</span></article>
          <article class="resourceCard grid-item blogs-and-news"><span class="resourceCard__body-title">Card 2<br>Type: Blog & News<br>Tag: Automation</span></article>
          <article class="resourceCard grid-item video-demos-tour automation ansible"><span class="resourceCard__body-title">Card 3<br>Type: Video Demo & Tour<br>Tags: Automation, Ansible</span></article>
          <article class="resourceCard grid-item blogs-and-news ansible"><span class="resourceCard__body-title">Card 4<br>Type: Blog & News<br>Tag: Ansible</span></article>
          <article class="resourceCard grid-item blogs-and-news ansible"><span class="resourceCard__body-title">Card 5<br>Type: Blog & News<br>Tags: Ansible, Automations</span></article>
        </div>
      </div>
      <!-- END -->

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


from Using isotope.js to define an AND relationship when filtering using multiple options

No comments:

Post a Comment