← back to Dw Theme Boost Fix
assets/dw-filter-horizontal.js
170 lines
/* ============================================================================
DW Horizontal Filter Bar — JS
Turns the Boost AI Search & Discovery vertical filter sidebar into a
horizontal, collapsible filter bar across the top of the product grid.
What it does (theme-side only, no Boost-dashboard change):
1. Waits for Boost's React-rendered .boost-sd__filter-tree to exist.
2. Adds body.dw-filters-horizontal so dw-filter-horizontal.css applies the
horizontal layout (each group becomes a chip with a dropdown panel).
3. Each group title (.boost-sd__filter-title) toggles a `.dw-open` class on
its .boost-sd__filter so only one dropdown shows at a time. Clicks on the
actual option items still bubble to Boost, so filtering still works.
4. Injects a single "Filters" pill into the Boost toolbar that collapses /
expands the whole bar; collapsed state persists in localStorage.
Replaces the old `dw-filters-collapsed` (hide-entirely) behavior on
collection/search pages. Only runs where a Boost filter tree is present.
============================================================================ */
(function () {
'use strict';
var BODY = document.body;
var LS_KEY = 'dw-hbar-collapsed';
var wired = false;
function isGridPage() {
return /\/(collections|search)/.test(window.location.pathname);
}
function qs(sel, root) { return (root || document).querySelector(sel); }
/* Toggle a single filter group open/closed (accordion: one at a time). */
function bindGroup(filterEl) {
if (filterEl.getAttribute('data-dw-hbar')) return;
filterEl.setAttribute('data-dw-hbar', '1');
var title = qs('.boost-sd__filter-title', filterEl);
if (!title) return;
title.addEventListener('click', function (e) {
// Only handle clicks on the title chip itself, not on option rows that
// Boost may have nested — let those reach Boost.
if (e.target.closest('.boost-sd__filter-option') ||
e.target.closest('.boost-sd__filter-options')) {
return;
}
var alreadyOpen = filterEl.classList.contains('dw-open');
// Close all siblings first
var tree = filterEl.closest('.boost-sd__filter-tree');
if (tree) {
tree.querySelectorAll('.boost-sd__filter.dw-open').forEach(function (f) {
if (f !== filterEl) f.classList.remove('dw-open');
});
}
filterEl.classList.toggle('dw-open', !alreadyOpen);
});
}
function bindAllGroups() {
var groups = document.querySelectorAll('.boost-sd__filter-tree .boost-sd__filter');
for (var i = 0; i < groups.length; i++) bindGroup(groups[i]);
}
/* Close any open dropdown when clicking outside the bar. */
function bindOutsideClose() {
document.addEventListener('click', function (e) {
if (e.target.closest('.boost-sd__filter-tree')) return;
document.querySelectorAll('.boost-sd__filter.dw-open').forEach(function (f) {
f.classList.remove('dw-open');
});
});
}
/* Build the collapse/expand toggle pill and drop it in the Boost toolbar. */
function buildToggle() {
if (qs('#dw-hbar-toggle')) return;
var toolbar = qs('.boost-sd__toolbar-item--product-count, .boost-sd__product-count');
if (!toolbar || !toolbar.parentElement) return;
var btn = document.createElement('button');
btn.id = 'dw-hbar-toggle';
btn.className = 'dw-hbar-toggle';
btn.type = 'button';
btn.setAttribute('aria-label', 'Toggle filter bar');
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
var ys = ['6', '12', '18'];
for (var i = 0; i < ys.length; i++) {
var l = document.createElementNS('http://www.w3.org/2000/svg', 'line');
l.setAttribute('x1', '4'); l.setAttribute('y1', ys[i]);
l.setAttribute('x2', '20'); l.setAttribute('y2', ys[i]);
svg.appendChild(l);
}
btn.appendChild(svg);
var label = document.createElement('span');
label.id = 'dw-hbar-label';
btn.appendChild(label);
function syncLabel() {
var collapsed = BODY.classList.contains('dw-hbar-collapsed');
label.textContent = collapsed ? 'Filters' : 'Hide Filters';
}
btn.addEventListener('click', function () {
var collapsed = BODY.classList.toggle('dw-hbar-collapsed');
try { localStorage.setItem(LS_KEY, collapsed ? '1' : '0'); } catch (e) {}
syncLabel();
});
toolbar.parentElement.insertBefore(btn, toolbar);
syncLabel();
}
/* Apply the persisted collapsed state. Default: bar EXPANDED (visible). */
function applyPersistedState() {
var collapsed = false;
try { collapsed = localStorage.getItem(LS_KEY) === '1'; } catch (e) {}
BODY.classList.toggle('dw-hbar-collapsed', collapsed);
}
function wire() {
if (wired) return true;
var tree = qs('.boost-sd__filter-tree');
if (!tree) return false;
wired = true;
// Remove the legacy hide-entirely behavior so the two don't fight.
BODY.classList.remove('dw-filters-collapsed');
BODY.classList.add('dw-filters-horizontal');
applyPersistedState();
bindAllGroups();
bindOutsideClose();
buildToggle();
// Boost re-renders the tree on filter apply / page change — re-bind groups
// and keep our toggle present.
var obs = new MutationObserver(function () {
clearTimeout(obs._t);
obs._t = setTimeout(function () {
bindAllGroups();
buildToggle();
}, 150);
});
var wrap = qs('.boost-sd__filter-tree-wrapper') || tree.parentElement || document.body;
obs.observe(wrap, { childList: true, subtree: true });
return true;
}
function start() {
if (!isGridPage()) return;
var tries = 0;
var poll = setInterval(function () {
if (wire() || tries++ > 80) clearInterval(poll);
}, 250);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})();