html 在使用多个选项进行过滤时,使用isotope.js定义AND关系

p8ekf7hl  于 2023-10-14  发布在  其他
关注(0)|答案(1)|浏览(119)

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

  1. type
  2. tag
    我试图利用isotope.js,以实现双重过滤布局,但它总是给最后点击过滤器优先级。
    我希望它的运作方式是:
    1.如果只选择了一个tag,则显示具有该tag的结果
    1.如果选择了多个tag,则显示具有任何这些标签的结果(两个标签不需要同时存在于卡上)
    1.如果选择了type,则仅显示该type下的结果
    1.如果选择了一个type和一个tag,则显示所选type上存在tag的帖子的结果
    1.如果选择了一个type和多个tags,则显示属于该类型的具有这些tags之一的帖子。
    1.如果选择了多个type和一个或多个tag,则显示其中tag存在于任一类型上的帖子
    例如,使用我下面的演示,这里有一些用例:
    1.如果我点击“视频演示和图尔斯”,我应该看到两个视频职位(卡1和3)-作品
    1.如果我点击“视频演示和图尔斯”,然后“Ansible”,我应该只看到卡3 -不工作
    1.如果我点击“博客&新闻”,我应该看到3卡(卡2,4,5)-作品
    1.如果我点击“博客和新闻”,然后点击“Ansible”,我应该会看到卡片4和5
    1.如果我点击“博客和新闻”,“Ansible”,然后“自动化”,我应该看到卡2,4和5
    然而,在我当前的演示中,尽管控制台日志似乎在正确的行上,但它并没有按照我的意图执行。
  1. document.addEventListener("DOMContentLoaded", function () {
  2. var container = document.querySelector(".grid");
  3. var gridItems = container.querySelectorAll(".grid-item");
  4. const optionLinks = document.querySelectorAll(".rSidebar__options-li");
  5. var iso = new Isotope(container, {
  6. itemSelector: ".resourceCard",
  7. layoutMode: "fitRows",
  8. transitionDuration: "0.5s"
  9. });
  10. var filters = {};
  11. function concatValues(obj) {
  12. var value = [];
  13. for (var prop in obj) {
  14. value.push(obj[prop]);
  15. }
  16. return value.flat().join(", ");
  17. }
  18. function handleFilterClick(event, filters) {
  19. var listItem = event;
  20. var filterGroup = listItem
  21. .closest(".rSidebar__options")
  22. .getAttribute("data-filter-group");
  23. var data_filter = listItem.getAttribute("data-filter");
  24. if (filterGroup === "type") {
  25. filters[filterGroup] = [data_filter];
  26. } else {
  27. if (!filters[filterGroup]) {
  28. filters[filterGroup] = [];
  29. }
  30. if (listItem.classList.contains("selected")) {
  31. filters[filterGroup].push(data_filter);
  32. } else {
  33. filters[filterGroup] = filters[filterGroup].filter(
  34. (tag) => tag !== data_filter
  35. );
  36. }
  37. }
  38. // Combine the type filter with the selected tag filters using an AND relationship
  39. var filterValues = [];
  40. // Handle type filter
  41. if (filters["type"]) {
  42. filterValues.push("." + filters["type"][0]);
  43. }
  44. // Handle tags filter if it's defined
  45. if (filters["tag"]) {
  46. var selectedType = filters["type"][0];
  47. filters["tag"].forEach((tag) => {
  48. filterValues.push("." + selectedType + "." + tag);
  49. });
  50. }
  51. var finalFilter = filterValues.join(", ");
  52. console.log(finalFilter);
  53. iso.arrange({
  54. filter: finalFilter
  55. });
  56. }
  57. optionLinks.forEach(function (optionLink) {
  58. optionLink.addEventListener("click", function (event) {
  59. event.preventDefault();
  60. this.classList.toggle("selected");
  61. handleFilterClick(this, filters);
  62. });
  63. });
  64. });
  1. .post {
  2. padding: 100px;
  3. }
  4. .rSidebar__box {
  5. margin-bottom: 30px;
  6. }
  7. .rSidebar__options {
  8. padding-left: 0;
  9. }
  10. .rSidebar__options-li {
  11. margin-bottom: 17px;
  12. display: flex;
  13. align-items: center;
  14. cursor: pointer;
  15. width: fit-content;
  16. }
  17. .rSidebar__options-li.selected .rSidebar__options-square {
  18. background-color: #185A7D;
  19. }
  20. .rSidebar__options-square {
  21. height: 20px;
  22. width: 20px;
  23. transition: all 0.5s ease;
  24. border: 2px solid #000000;
  25. }
  26. .rSidebar__options-label {
  27. margin-left: 10px;
  28. }
  29. .grid {
  30. display: flex;
  31. flex-wrap: wrap;
  32. margin: -14px 0 0 -14px;
  33. }
  34. .grid-item {
  35. box-sizing: border-box;
  36. width: calc(45% - 14px);
  37. margin: 14px 0 18px 14px;
  38. border: 2px solid black;
  39. padding: 20px;
  40. }
  1. <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  2. <script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
  3. <div class="post">
  4. <div class="container">
  5. <div class="row justify-content-between">
  6. <!-- SIDEBAR -->
  7. <div class="col-3">
  8. <div class="rSidebar">
  9. <!-- tags -->
  10. <div class="rSidebar__box">
  11. <span class="rSidebar__label d-block fw-bold">Filter by tag</span>
  12. <ul class="rSidebar__options button-group" data-filter-group="tag">
  13. <li class="rSidebar__options-li" data-filter="ansible">
  14. <span class="rSidebar__options-square"></span>
  15. <span class="rSidebar__options-label d-block" data-filter="ansible">Ansible</span>
  16. </li>
  17. <li class="rSidebar__options-li" data-filter="automation">
  18. <span class="rSidebar__options-square"></span>
  19. <span class="rSidebar__options-label d-block" data-filter="automation">Automation</span>
  20. </li>
  21. </ul>
  22. </div>
  23. <!-- type -->
  24. <div class="rSidebar__box">
  25. <span class="rSidebar__label d-block fw-bold">Filter by type</span>
  26. <ul class="rSidebar__options button-group" data-filter-group="type">
  27. <li class="rSidebar__options-li" data-filter="blogs-and-news">
  28. <span class="rSidebar__options-square"></span>
  29. <span class="rSidebar__options-label d-block" data-filter="blogs-and-news">Blog & News</span>
  30. </li>
  31. <li class="rSidebar__options-li" data-filter="video-demos-tour">
  32. <span class="rSidebar__options-square"></span>
  33. <span class="rSidebar__options-label d-block" data-filter="video-demos-tours">Video Demos & Tours</span>
  34. </li>
  35. </ul>
  36. </div>
  37. <!-- end -->
  38. </div>
  39. </div>
  40. <!-- END -->
  41. <!-- GRID -->
  42. <div class="col-7">
  43. <div class="grid">
  44. <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>
  45. <article class="resourceCard grid-item blogs-and-news"><span class="resourceCard__body-title">Card 2<br>Type: Blog & News<br>Tag: Automation</span></article>
  46. <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>
  47. <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>
  48. <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>
  49. </div>
  50. </div>
  51. <!-- END -->
  52. </div>
  53. </div>
  54. </div>
hgqdbh6s

hgqdbh6s1#

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

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

在选择器中(如Isotope所使用的),这将是:

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

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

  1. function buildSelectors(filterObject){
  2. return Object.values(filterObject) // extract filter arrays
  3. .reduce(
  4. (selectors, groupValues) =>
  5. groupValues.length === 0 ? selectors : // skip empty groups
  6. groupValues.flatMap(v => selectors.map(s => `${s}.${v}`)) // combine group elements with existing selectors into new list
  7. , [''])
  8. .join(', ')
  9. }

如果你更喜欢常规循环,这是这样的:

  1. function buildSelectors(filterObject){
  2. let selectors = ['']
  3. for (const group of Object.values(filterObject)){
  4. if (group.length === 0) {
  5. continue
  6. }
  7. const mergedSelectors = []
  8. for (const value of group) {
  9. for (const selector of selectors) {
  10. mergedSelectors.push(`${selector}.${value}`)
  11. }
  12. }
  13. selectors = mergedSelectors
  14. }
  15. return selectors.join(',')
  16. }

一些一般提示和建议:

  • 卡2和卡5错过了automation类,不确定这是否是故意的
  • 使用所有现有组的空数组示例化filter对象,这样就可以避免您以后弄清楚组属性是否已经存在
  • 将DOM操作与过滤数据操作分离,使代码更易于维护
  • 考虑为Filter创建一个类,以便进一步按关注点分割代码

看看更新的片段:

  1. class Filter{
  2. groups = {
  3. tag: [],
  4. type: [],
  5. }
  6. updateGroup(group, value, isSelected) {
  7. const isActiveValue = this.groups[group].includes(value)
  8. if (isSelected && !isActiveValue){
  9. this.groups[group].push(value)
  10. }
  11. if (!isSelected && isActiveValue){
  12. this.groups[group] = this.groups[group].filter(v => v !== value)
  13. }
  14. }
  15. buildSelector(){
  16. return Object.values(this.groups).reduce(
  17. (selectors, groupValues) =>
  18. groupValues.length === 0 ? selectors :
  19. groupValues.flatMap(v => selectors.map(s => `${s}.${v}`))
  20. , [''])
  21. .join(', ')
  22. }
  23. updateSelector(group, value, isSelected){
  24. this.updateGroup(group, value, isSelected)
  25. return this.buildSelector()
  26. }
  27. }
  28. document.addEventListener("DOMContentLoaded", function () {
  29. const iso = new Isotope('.grid', {
  30. itemSelector: ".resourceCard",
  31. layoutMode: "fitRows",
  32. transitionDuration: "0.5s"
  33. });
  34. const activeFilter = new Filter()
  35. function onOptionLinkClick(event){
  36. event.preventDefault();
  37. this.classList.toggle("selected");
  38. const isSelected = this.classList.contains("selected")
  39. const group = this
  40. .closest(".rSidebar__options")
  41. .getAttribute("data-filter-group");
  42. const value = this.getAttribute("data-filter");
  43. const filter = activeFilter.updateSelector(group, value, isSelected)
  44. iso.arrange({filter})
  45. console.clear()
  46. console.log('Current filter:', filter)
  47. }
  48. const optionLinks = document.querySelectorAll(".rSidebar__options-li");
  49. optionLinks.forEach(optionLink => optionLink.addEventListener("click", onOptionLinkClick));
  50. })
  1. .post {
  2. padding: 10px;
  3. }
  4. .rSidebar__box {
  5. margin-bottom: 30px;
  6. }
  7. .rSidebar__options {
  8. padding-left: 0;
  9. }
  10. .rSidebar__options-li {
  11. margin-bottom: 17px;
  12. display: flex;
  13. align-items: center;
  14. cursor: pointer;
  15. width: fit-content;
  16. }
  17. .rSidebar__options-li.selected .rSidebar__options-square {
  18. background-color: #185A7D;
  19. }
  20. .rSidebar__options-square {
  21. height: 20px;
  22. width: 20px;
  23. transition: all 0.5s ease;
  24. border: 2px solid #000000;
  25. }
  26. .rSidebar__options-label {
  27. margin-left: 10px;
  28. }
  29. .grid {
  30. display: flex;
  31. flex-wrap: wrap;
  32. margin: -14px 0 0 -14px;
  33. }
  34. .grid-item {
  35. box-sizing: border-box;
  36. width: calc(45% - 14px);
  37. margin: 14px 0 18px 14px;
  38. border: 2px solid black;
  39. padding: 20px;
  40. }
  1. <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  2. <script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
  3. <div class="post">
  4. <div class="container">
  5. <div class="row justify-content-between">
  6. <!-- SIDEBAR -->
  7. <div class="col-3">
  8. <div class="rSidebar">
  9. <!-- tags -->
  10. <div class="rSidebar__box">
  11. <span class="rSidebar__label d-block fw-bold">Filter by tag</span>
  12. <ul class="rSidebar__options button-group" data-filter-group="tag">
  13. <li class="rSidebar__options-li" data-filter="ansible">
  14. <span class="rSidebar__options-square"></span>
  15. <span class="rSidebar__options-label d-block" data-filter="ansible">Ansible</span>
  16. </li>
  17. <li class="rSidebar__options-li" data-filter="automation">
  18. <span class="rSidebar__options-square"></span>
  19. <span class="rSidebar__options-label d-block" data-filter="automation">Automation</span>
  20. </li>
  21. </ul>
  22. </div>
  23. <!-- type -->
  24. <div class="rSidebar__box">
  25. <span class="rSidebar__label d-block fw-bold">Filter by type</span>
  26. <ul class="rSidebar__options button-group" data-filter-group="type">
  27. <li class="rSidebar__options-li" data-filter="blogs-and-news">
  28. <span class="rSidebar__options-square"></span>
  29. <span class="rSidebar__options-label d-block" data-filter="blogs-and-news">Blog & News</span>
  30. </li>
  31. <li class="rSidebar__options-li" data-filter="video-demos-tour">
  32. <span class="rSidebar__options-square"></span>
  33. <span class="rSidebar__options-label d-block" data-filter="video-demos-tours">Video Demos & Tours</span>
  34. </li>
  35. </ul>
  36. </div>
  37. <!-- end -->
  38. </div>
  39. </div>
  40. <!-- END -->
  41. <!-- GRID -->
  42. <div class="col-7">
  43. <div class="grid">
  44. <article class="resourceCard grid-item video-demos-tour automation"><span class="resourceCard__body-title">Card 1<br>Type: Video<br>Tag: Automation</span></article>
  45. <article class="resourceCard grid-item blogs-and-news automation"><span class="resourceCard__body-title">Card 2<br>Type: Blog<br>Tag: Automation</span></article>
  46. <article class="resourceCard grid-item video-demos-tour automation ansible"><span class="resourceCard__body-title">Card 3<br>Type: Video<br>Tags: Automation, Ansible</span></article>
  47. <article class="resourceCard grid-item blogs-and-news ansible"><span class="resourceCard__body-title">Card 4<br>Type: Blog<br>Tag: Ansible</span></article>
  48. <article class="resourceCard grid-item blogs-and-news ansible automation"><span class="resourceCard__body-title">Card 5<br>Type: Blog<br>Tags: Ansible, Automation</span></article>
  49. <article class="resourceCard grid-item video-demos-tour ansible"><span class="resourceCard__body-title">Card 6<br>Type: Video <br>Tags: Ansible</span></article>
  50. </div>
  51. </div>
  52. <!-- END -->
  53. </div>
  54. </div>
  55. </div>

这有意义吗,有帮助吗?

展开查看全部

相关问题