1(function () { 2 const { mergeObjects, matchMedia, onResize } = BreakdanceFrontend.utils; 3 4 class BreakdanceFilter { 5 defaultOptions = { 6 wrapperSelector: ".ee-posts", 7 itemSelector: ".ee-post", 8 sizerSelector: ".ee-post-sizer", 9 gutterSelector: ".ee-post-gutter", 10 defaultActiveIndex: 0, 11 layout: "grid", 12 horizontalAt: "breakpoint_phone_landscape", 13 isVertical: false, 14 equalHeight: false 15 }; 16 items = []; 17 filterActiveClass = "is-active"; 18 19 constructor(selector, options) { 20 this.onResize = this.onResize.bind(this); 21 this.onImagesLoaded = this.onImagesLoaded.bind(this); 22 this.onFilterChange = this.onFilterChange.bind(this); 23 this.selector = selector; 24 this.options = mergeObjects(this.defaultOptions, options); 25 26 this.init(); 27 } 28 29 isEqualHeight() { 30 const isMasonry = this.options.layout === "masonry"; 31 return this.options.equalHeight && !isMasonry && !this.isSlider(); 32 } 33 34 isSlider() { 35 return this.options.layout === "slider"; 36 } 37 38 /* 39 * Turn DOM Elements into Isotope-ready Items 40 * This is required in order to make Isotope work in the Builder. 41 */ 42 itemize(items) { 43 return items.map((elem) => new Isotope.Item(elem, this.iso)); 44 } 45 46 /* 47 * Force Isotope to accept our DOM Elements. 48 * This is required due to a bug in the library. 49 * "elem instanceof HTMLCollection" always return false inside same-origin iframes 50 */ 51 appendItems(elems) { 52 const items = this.itemize(elems); 53 54 if (!items.length) return; 55 56 this.iso.items = items; 57 this.iso.filteredItems = items; 58 } 59 60 relayout() { 61 if (!this.iso) return; 62 63 if (this.isEqualHeight()) { 64 this.equalizeHeights(); 65 } 66 67 this.iso.layout(); 68 } 69 70 getItemElems() { 71 return Array.from(this.wrapperEl.querySelectorAll(this.options.itemSelector)); 72 } 73 74 onImagesLoaded() { 75 this.relayout(); 76 } 77 78 setActiveButton(activeButton) { 79 this.filterButtons.forEach((button) => { 80 button.classList.remove(this.filterActiveClass); 81 button.setAttribute("aria-selected", "false"); 82 }); 83 84 activeButton.classList.add(this.filterActiveClass); 85 activeButton.setAttribute("aria-selected", "true"); 86 } 87 88 // Filters 89 getFilterValue(node) { 90 if (node.tagName === "SELECT") { 91 const selected = node.options[node.selectedIndex]; 92 return selected.dataset.value; 93 } 94 95 return node.dataset.value; 96 } 97 98 filterBy(node) { 99 const filter = this.getFilterValue(node); 100 const button = this.filterButtons.find((button) => button.id === node.id); 101 102 const filterByTerms = (item) => { 103 const curFilters = (item.dataset.filters ?? '').split(","); 104 return curFilters.includes(filter); 105 }; 106 107 if (this.isSlider()) { 108 this.filterBySlider(filter, filterByTerms); 109 } else { 110 this.filterByIsotope(filter, filterByTerms); 111 } 112 113 if (button) { 114 this.setActiveButton(button); 115 } 116 } 117 118 filterBySlider(filter, filterFn) { 119 const swiperEl = this.element.querySelector('.swiper'); 120 const itemsToShow = filter === "all" ? this.items : this.items.filter(filterFn); 121 const itemsToHide = this.items.filter(x => !itemsToShow.includes(x)); 122 123 itemsToShow.forEach(item => item.style.display = null); 124 itemsToHide.forEach(item => item.style.display = 'none'); 125 126 swiperEl.swiper.update(); 127 swiperEl.swiper.slideTo(0, 0); 128 } 129 130 filterByIsotope(filter, filterFn) { 131 if (filter === "all") { 132 this.iso.arrange({ 133 layoutInstant: false, 134 filter: "*" 135 }); 136 } else { 137 this.iso.arrange({ 138 layoutInstant: false, 139 filter(element) { 140 return filterFn(element); 141 }, 142 }); 143 } 144 } 145 146 onFilterChange(event) { 147 event.preventDefault(); 148 this.filterBy(event.currentTarget); 149 } 150 151 initFilters() { 152 this.filterButtons = Array.from(this.element.querySelectorAll(".js-tab")); 153 const mobileFilter = this.element.querySelector(".js-tab-select"); 154 155 this.filterButtons.forEach((button, index) => { 156 if (index === this.options.defaultActiveIndex) { 157 this.setActiveButton(button); 158 } 159 160 button.addEventListener("click", this.onFilterChange, { signal: this.ac.signal }); 161 }); 162 163 if (mobileFilter) { 164 mobileFilter.addEventListener("change", this.onFilterChange, { signal: this.ac.signal }); 165 } 166 } 167 168 equalizeHeights() { 169 const grid = this.wrapperEl; 170 const items = this.getItemElems() 171 .filter(item => item.style.display !== "none"); 172 173 const itemsPerRow = Math.floor(grid.clientWidth / items[0].offsetWidth); 174 let rowHeight = 0; 175 176 // Reset heights
177 items.forEach(item => { 178 item.style.height = "auto"; 179 }); 180 181 for (let i = 0; i < items.length; i += itemsPerRow) { 182 const rowItems = items.slice(i, i + itemsPerRow); 183 rowHeight = Math.max(...rowItems.map(item => item.offsetHeight)); 184 rowItems.forEach(item => item.style.height = `${rowHeight}px`); 185 } 186 } 187 188 resizeVertical() { 189 if (!this.options.isVertical) return; 190 const breakpoint = this.options.horizontalAt; 191 const { BASE_BREAKPOINT_ID } = window.BreakdanceFrontend.data; 192 193 const tabList = this.element.querySelector(".js-tablist"); 194 const tabsContainer = tabList.closest(".js-tabs-container"); 195 196 if ( 197 matchMedia(breakpoint) || 198 breakpoint === BASE_BREAKPOINT_ID 199 ) { 200 tabsContainer.classList.remove('is-vertical'); 201 tabList.setAttribute("aria-orientation", "horizontal"); 202 } else { 203 tabsContainer.classList.add('is-vertical'); 204 tabList.setAttribute("aria-orientation", "vertical"); 205 } 206 } 207 208 onResize() { 209 this.relayout(); 210 this.resizeVertical(); 211 } 212 213 destroy() { 214 if (this.iso) { 215 this.iso.destroy(); 216 this.iso = null; 217 } 218 219 if (this.ac) { 220 this.ac.abort(); 221 this.ac = null; 222 } 223 224 this.disconnect(); 225 226 // Reset heights 227 this.getItemElems() 228 .filter(item => item.style.display !== "none") 229 .forEach(item => { 230 item.style.removeProperty("height"); 231 }); 232 233 imagesLoaded(this.element).off("progress", this.onImagesLoaded); 234 } 235 236 update(options) { 237 this.options = mergeObjects(this.defaultOptions, options); 238 this.destroy(); 239 this.init(); 240 } 241 242 initIsotope() { 243 const layoutMode = this.isEqualHeight() ? "fitRows" : "masonry"; 244 245 // Remove display: none from initially hidden items 246 this.items.forEach(item => item.style.display = null); 247 248 this.iso = new Isotope(this.wrapperEl, { 249 itemSelector: this.options.itemSelector, 250 percentPosition: true, 251 isJQueryFiltering: false, 252 layoutMode, 253 fitRows: { 254 columnWidth: this.options.sizerSelector, 255 gutter: this.options.gutterSelector, 256 }, 257 masonry: { 258 columnWidth: this.options.sizerSelector, 259 gutter: this.options.gutterSelector, 260 } 261 }); 262 263 this.appendItems(this.items); 264 265 // Hide initially hidden items by default 266 this.iso.arrange({ 267 layoutInstant: true, 268 filter: ":not(.initially-hidden)" 269 }); 270 271 this.iso.on("arrangeComplete", () => { 272 if (this.isEqualHeight()) { 273 this.relayout(); 274 } 275 }); 276 277 this.element.addEventListener("breakdance_infinite_scroll_loaded", (event) => { 278 const appendedElems = Array.from(event.detail || []); 279 if (!appendedElems.length) return; 280 281 this.iso.appended(appendedElems); 282 283 imagesLoaded(appendedElems) 284 .on("always", () => this.relayout()); 285 }, { signal: this.ac.signal }); 286 287 // Force Isotope to relayout when clicking on a link or button. 288 // This is required if the link or button causes the element to change size. 289 this.wrapperEl.querySelectorAll("a, button").forEach((el) => { 290 el.addEventListener("click", () => this.iso.layout(), { signal: this.ac.signal }); 291 }); 292 293 imagesLoaded(this.element).on("progress", this.onImagesLoaded); 294 } 295 296 init() { 297 this.element = document.querySelector(this.selector); 298 this.wrapperEl = this.element.querySelector(this.options.wrapperSelector); 299 this.items = this.getItemElems(); 300 301 if (this.ac) this.ac.abort(); 302 this.ac = new AbortController(); 303 304 if (!this.isSlider()) { 305 this.initIsotope(); 306 } 307 308 this.initFilters(); 309 310 this.disconnect = onResize(this.onResize); 311 this.resizeVertical(); 312 } 313 } 314 315 window.BreakdanceFilter = BreakdanceFilter; 316})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.