← back to NationalPaperHangers
public/js/find-filter.js
114 lines
/**
* find-filter.js — debounced fetch-on-change for /find filters
* Intercepts form changes + submission, fetches rendered HTML from the server,
* replaces .installer-grid and .result-count with updated content via DOMParser.
* Uses history.pushState so the URL stays bookmarkable.
*/
(function () {
'use strict';
var form = document.querySelector('.find-filters');
if (!form) return;
var grid = document.querySelector('.installer-grid');
var resultCount = document.querySelector('.result-count');
var debounceTimer = null;
var currentController = null;
function buildUrl() {
var params = new URLSearchParams(new FormData(form));
// remove blank values to keep URLs clean
for (var key of Array.from(params.keys())) {
if (!params.get(key)) params.delete(key);
}
return '/find?' + params.toString();
}
function showLoading() {
if (grid) {
grid.style.opacity = '0.45';
grid.style.pointerEvents = 'none';
}
if (resultCount) {
resultCount.dataset.original = resultCount.textContent;
resultCount.textContent = 'Loading…';
}
}
function hideLoading() {
if (grid) {
grid.style.opacity = '';
grid.style.pointerEvents = '';
}
}
function doFetch() {
var url = buildUrl();
// abort any in-flight request
if (currentController) currentController.abort();
currentController = new AbortController();
showLoading();
history.pushState(null, '', url);
fetch(url, {
headers: { 'Accept': 'text/html', 'X-Requested-With': 'XMLHttpRequest' },
signal: currentController.signal
})
.then(function (res) {
if (!res.ok) throw new Error('Bad response: ' + res.status);
return res.text();
})
.then(function (html) {
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var newGrid = doc.querySelector('.installer-grid');
var newCount = doc.querySelector('.result-count');
if (newGrid && grid) {
grid.innerHTML = newGrid.innerHTML;
}
if (newCount && resultCount) {
resultCount.textContent = newCount.textContent;
}
hideLoading();
})
.catch(function (err) {
if (err.name === 'AbortError') return; // superseded by newer request
hideLoading();
if (resultCount && resultCount.dataset.original) {
resultCount.textContent = resultCount.dataset.original;
}
console.warn('[find-filter] fetch error:', err);
});
}
function scheduleSearch() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(doFetch, 300);
}
// Intercept native submit
form.addEventListener('submit', function (e) {
e.preventDefault();
clearTimeout(debounceTimer);
doFetch();
});
// Debounce on any input or select change
form.addEventListener('input', scheduleSearch);
form.addEventListener('change', scheduleSearch);
// Handle browser back/forward
window.addEventListener('popstate', function () {
// Re-sync form from URL then fetch
var params = new URLSearchParams(window.location.search);
form.querySelectorAll('input, select').forEach(function (el) {
if (el.name) el.value = params.get(el.name) || '';
});
doFetch();
});
})();