使用 isotope.js 定义多个选项过滤时的 AND 关系

问题描述 投票:0回答:1

我有两个内容类型区域,其中包含独特的过滤器选项。这些是:

  1. type
  2. tag

我正在尝试利用 isotope.js 来实现双重过滤布局,但它总是给予最后单击的过滤器优先级。

我希望它发挥作用的方式是:

  1. 如果仅选择一个
    tag
    ,则显示带有该
    tag
  2. 的结果
  3. 如果选择了多个
    tag
    ,则显示具有任何这些标签的结果(两者不需要同时存在于一张卡片上)
  4. 如果选择了
    type
    ,则仅显示属于该
    type
  5. 的结果
  6. 如果选择了一个
    type
    和一个
    tag
    ,则显示所选
    tag
     上存在 
    type
  7. 的帖子结果
  8. 如果选择了一个
    type
    并选择了多个
    tags
    ,则显示属于该类型且具有其中任意一个
    tags
    的帖子。
  9. 如果选择了多个
    type
    和一个或多个
    tag
    ,则显示任一类型中存在
    tag
    的帖子

例如,使用下面的演示,以下是一些用例:

  1. 如果我点击“视频演示和游览”,我应该会看到两个视频帖子(卡 1 和 3)- 有效
  2. 如果我点击“Video Demos & Tours”,然后点击“Ansible”,我应该只能看到卡片 3 - 不起作用
  3. 如果我点击“博客和新闻”,我应该会看到 3 张卡片(卡片 2、4、5)- 有效
  4. 如果我点击“博客和新闻”,然后点击“Ansible”,我应该会看到卡片 4 和 5
  5. 如果我点击“博客和新闻”、“Ansible”,然后点击“自动化”,我应该会看到卡片 2,4 和 5

但是,在我当前的演示中,虽然控制台日志似乎位于正确的行,但它并没有按照我的意图执行。

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/[email protected]/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>

javascript html jquery css jquery-isotope
1个回答
0
投票

如果我理解正确的话,你想用 OR 组合同一组的过滤器,用 AND 组合组之间的过滤器:

{a,b} x {1,2} -> a1|a2|b1|b2

在选择器中(同位素使用的),这将是:

.a.1, .a.2, .b.1, .b.2

您可以通过遍历每个组,将每个元素与之前的构建选择器组合来构建它:

function buildFilterValues(){
  return Object.values(filters)  // extract filter arrays
    .reduce(
      (selectors, groupValues) => 
        groupValues.length === 0 ? selectors :  // skip empty groups
        groupValues.flatMap(v => selectors.map(s => `${s}.${v}`)) // combine group elements with existing selectors into new list
      , [''])
    .join(', ')
}

一些一般提示和建议:

  • 卡 2 错过了
    automation
  • 使用所有现有组的空数组实例化
    filter
    对象,这样您就无需稍后再弄清楚组属性是否已存在
  • 将 DOM 操作与过滤数据操作分开,使代码更易于维护
  • 考虑为过滤器创建一个类,以进一步按关注点拆分代码

看看更新后的片段:

class Filter{
  groups = {
    tag: [],
    type: [],
  }
  
  updateGroup(group, value, isSelected) {
    const isActiveValue = this.groups[group].includes(value)
    if (isSelected && !isActiveValue){
       this.groups[group].push(value)
    }
    if (!isSelected && isActiveValue){
       this.groups[group] = this.groups[group].filter(v => v !== value)
    }
  }

  buildSelector(){
    return Object.values(this.groups).reduce(
      (selectors, groupValues) => 
        groupValues.length === 0 ? selectors :
        groupValues.flatMap(v => selectors.map(s => `${s}.${v}`))
      ,[''] )
    .join(', ')
  }
  
  updateSelector(group, value, isSelected){
    this.updateGroup(group, value, isSelected)
    return this.buildSelector()
  }
}

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

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

  const activeFilter = new Filter()
  
  function onOptionLinkClick(event){
    event.preventDefault();
    
    this.classList.toggle("selected");
    const isSelected = this.classList.contains("selected")
    
    const group = this
      .closest(".rSidebar__options")
      .getAttribute("data-filter-group");
    const value = this.getAttribute("data-filter");

    const filter = activeFilter.updateSelector(group, value, isSelected)
    iso.arrange({filter})
    
    console.clear()
    console.log('Current filter:', filter)
  }

  const optionLinks = document.querySelectorAll(".rSidebar__options-li");
  optionLinks.forEach(optionLink => optionLink.addEventListener("click", onOptionLinkClick));
})
.post {
  padding: 10px;
}

.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/[email protected]/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 automation"><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>

这有意义吗?对你有用吗?

© www.soinside.com 2019 - 2024. All rights reserved.