| 1 |
/** |
| 2 |
* Live filtering for the Integrations page. |
| 3 |
* |
| 4 |
* Every card is already in the DOM — there are around fifteen — so filtering |
| 5 |
* happens here rather than as a round trip per click, which would be slower and |
| 6 |
* would throw away the scroll position for nothing. |
| 7 |
* |
| 8 |
* The filter rail ships with a `hidden` attribute and is revealed here, so a |
| 9 |
* browser with no JavaScript shows the full, unfiltered list rather than |
| 10 |
* controls that do nothing. |
| 11 |
*/ |
| 12 |
(function () { |
| 13 |
"use strict"; |
| 14 |
|
| 15 |
var root = document.querySelector("[data-subscrpt-integration-filters]"); |
| 16 |
|
| 17 |
if (!root) { |
| 18 |
return; |
| 19 |
} |
| 20 |
|
| 21 |
var cards = Array.prototype.slice.call(document.querySelectorAll("[data-subscrpt-int-card]")); |
| 22 |
|
| 23 |
if (!cards.length) { |
| 24 |
return; |
| 25 |
} |
| 26 |
|
| 27 |
var sections = Array.prototype.slice.call(document.querySelectorAll("[data-subscrpt-int-section]")); |
| 28 |
var summary = root.querySelector("[data-subscrpt-int-summary]"); |
| 29 |
var reset = root.querySelector("[data-subscrpt-int-reset]"); |
| 30 |
var empty = document.querySelector("[data-subscrpt-int-empty]"); |
| 31 |
var chips = Array.prototype.slice.call(root.querySelectorAll("[data-subscrpt-int-chip]")); |
| 32 |
|
| 33 |
var total = cards.length; |
| 34 |
|
| 35 |
// Category and status are single-select: a card has exactly one of each, so |
| 36 |
// picking two could only ever return nothing. Tags stay multi-select, because |
| 37 |
// "show me Pro and Beta" is a reasonable thing to ask. |
| 38 |
var SINGLE = ["category", "status"]; |
| 39 |
|
| 40 |
var state = { category: "", status: "", tag: [] }; |
| 41 |
|
| 42 |
function matches(card) { |
| 43 |
if (state.category && card.dataset.category !== state.category) { |
| 44 |
return false; |
| 45 |
} |
| 46 |
|
| 47 |
if (state.status && card.dataset.status !== state.status) { |
| 48 |
return false; |
| 49 |
} |
| 50 |
|
| 51 |
if (state.tag.length) { |
| 52 |
var tags = (card.dataset.tags || "").split(" "); |
| 53 |
var hasAll = state.tag.every(function (t) { |
| 54 |
return tags.indexOf(t) !== -1; |
| 55 |
}); |
| 56 |
if (!hasAll) { |
| 57 |
return false; |
| 58 |
} |
| 59 |
} |
| 60 |
|
| 61 |
return true; |
| 62 |
} |
| 63 |
|
| 64 |
// How many cards a chip would show: the current filters, but with this chip's |
| 65 |
// own facet swapped to its value (a facet never constrains its own counts; |
| 66 |
// the other facets do). Powers the live count next to each chip. |
| 67 |
function countFor(facet, value) { |
| 68 |
return cards.filter(function (card) { |
| 69 |
if (facet !== "category" && state.category && card.dataset.category !== state.category) { |
| 70 |
return false; |
| 71 |
} |
| 72 |
if (facet !== "status" && state.status && card.dataset.status !== state.status) { |
| 73 |
return false; |
| 74 |
} |
| 75 |
|
| 76 |
var tags = (card.dataset.tags || "").split(" "); |
| 77 |
|
| 78 |
// Respect the other selected tags; for a tag chip, ignore its own state |
| 79 |
// and instead require the card to carry this tag. |
| 80 |
var otherTags = facet === "tag" ? state.tag.filter((t) => t !== value) : state.tag; |
| 81 |
var hasOthers = otherTags.every(function (t) { |
| 82 |
return tags.indexOf(t) !== -1; |
| 83 |
}); |
| 84 |
if (!hasOthers) { |
| 85 |
return false; |
| 86 |
} |
| 87 |
if (facet === "tag" && tags.indexOf(value) === -1) { |
| 88 |
return false; |
| 89 |
} |
| 90 |
|
| 91 |
// Single-select chips (a specific value): the card must carry it. |
| 92 |
if (facet === "category" && value && card.dataset.category !== value) { |
| 93 |
return false; |
| 94 |
} |
| 95 |
if (facet === "status" && value && card.dataset.status !== value) { |
| 96 |
return false; |
| 97 |
} |
| 98 |
|
| 99 |
return true; |
| 100 |
}).length; |
| 101 |
} |
| 102 |
|
| 103 |
function updateCounts() { |
| 104 |
chips.forEach(function (chip) { |
| 105 |
var out = chip.querySelector(".subscrpt-int-chip__count"); |
| 106 |
if (!out) { |
| 107 |
return; |
| 108 |
} |
| 109 |
var n = countFor(chip.dataset.facet, chip.dataset.value); |
| 110 |
out.textContent = n; |
| 111 |
|
| 112 |
// Dim (and disable) a chip that would return nothing, unless it's the |
| 113 |
// currently active one — that must stay clickable to switch off. |
| 114 |
var active = chip.classList.contains("is-active"); |
| 115 |
var dead = n === 0 && !active; |
| 116 |
chip.style.opacity = dead ? "0.4" : ""; |
| 117 |
chip.style.pointerEvents = dead ? "none" : ""; |
| 118 |
}); |
| 119 |
} |
| 120 |
|
| 121 |
function apply() { |
| 122 |
var visible = 0; |
| 123 |
|
| 124 |
cards.forEach(function (card) { |
| 125 |
var show = matches(card); |
| 126 |
card.hidden = !show; |
| 127 |
if (show) { |
| 128 |
visible += 1; |
| 129 |
} |
| 130 |
}); |
| 131 |
|
| 132 |
// A heading over nothing reads as a bug, so hide a section whose cards have |
| 133 |
// all been filtered away. |
| 134 |
sections.forEach(function (section) { |
| 135 |
section.hidden = !section.querySelector("[data-subscrpt-int-card]:not([hidden])"); |
| 136 |
}); |
| 137 |
|
| 138 |
var filtering = !!state.category || !!state.status.length || !!state.tag.length; |
| 139 |
|
| 140 |
if (summary) { |
| 141 |
summary.textContent = filtering |
| 142 |
? summary.dataset.filtered.replace("%1$s", visible).replace("%2$s", total) |
| 143 |
: summary.dataset.all.replace("%s", total); |
| 144 |
} |
| 145 |
|
| 146 |
if (reset) { |
| 147 |
reset.hidden = !filtering; |
| 148 |
} |
| 149 |
|
| 150 |
if (empty) { |
| 151 |
empty.hidden = visible !== 0; |
| 152 |
} |
| 153 |
|
| 154 |
updateCounts(); |
| 155 |
} |
| 156 |
|
| 157 |
chips.forEach(function (chip) { |
| 158 |
chip.addEventListener("click", function () { |
| 159 |
var facet = chip.dataset.facet; |
| 160 |
var value = chip.dataset.value; |
| 161 |
|
| 162 |
if (SINGLE.indexOf(facet) !== -1) { |
| 163 |
// Selecting one clears the other in the same group, and clicking the |
| 164 |
// selected one again returns the group to "All". |
| 165 |
state[facet] = state[facet] === value ? "" : value; |
| 166 |
chips |
| 167 |
.filter(function (c) { |
| 168 |
return c.dataset.facet === facet; |
| 169 |
}) |
| 170 |
.forEach(function (c) { |
| 171 |
var on = c.dataset.value === state[facet]; |
| 172 |
c.classList.toggle("is-active", on); |
| 173 |
if (c.hasAttribute("aria-pressed")) { |
| 174 |
c.setAttribute("aria-pressed", on ? "true" : "false"); |
| 175 |
} |
| 176 |
}); |
| 177 |
} else { |
| 178 |
var list = state[facet]; |
| 179 |
var at = list.indexOf(value); |
| 180 |
if (at === -1) { |
| 181 |
list.push(value); |
| 182 |
} else { |
| 183 |
list.splice(at, 1); |
| 184 |
} |
| 185 |
var on = list.indexOf(value) !== -1; |
| 186 |
chip.classList.toggle("is-active", on); |
| 187 |
chip.setAttribute("aria-pressed", on ? "true" : "false"); |
| 188 |
} |
| 189 |
|
| 190 |
apply(); |
| 191 |
}); |
| 192 |
}); |
| 193 |
|
| 194 |
if (reset) { |
| 195 |
reset.addEventListener("click", function () { |
| 196 |
state = { category: "", status: "", tag: [] }; |
| 197 |
chips.forEach(function (c) { |
| 198 |
var isAll = SINGLE.indexOf(c.dataset.facet) !== -1 && c.dataset.value === ""; |
| 199 |
c.classList.toggle("is-active", isAll); |
| 200 |
if (c.hasAttribute("aria-pressed")) { |
| 201 |
c.setAttribute("aria-pressed", "false"); |
| 202 |
} |
| 203 |
}); |
| 204 |
apply(); |
| 205 |
}); |
| 206 |
} |
| 207 |
|
| 208 |
root.hidden = false; |
| 209 |
apply(); |
| 210 |
})(); |
| 211 |
|