Mackie

Welche Maße soll dein Akku haben?
(function () { "use strict"; var FIELDS = ["heightMin", "heightMax", "widthMin", "widthMax", "lengthMin", "lengthMax"]; var DEBOUNCE_MS = 300; /* Wartezeit nach der letzten Eingabe, bevor die volle Liste nachgeladen wird. Lang genug, um niemanden mitten im Tippen zu unterbrechen. */ var RELOAD_DEBOUNCE_MS = 1500; /* Seitengröße für die Voll-Liste. Wird serverseitig ohne Abgleich mit der "Artikel pro Seite"-Konfiguration übernommen. Bewusst moderat (flotte Ladezeit); auf die Akku-Unterkategorien zugeschnitten. Hat eine Kategorie mehr Artikel, greifen Folgeseiten-Status und Pagination-Sync (Filter bleibt beim Blättern aktiv). */ var FULL_LIST_ITEMS = 100; var debounceTimer = null; var reloadTimer = null; var observer = null; function getWidget() { return document.querySelector(".mf-masse-filter"); } function getInput(field) { var widget = getWidget(); return widget ? widget.querySelector('[data-mf-field="' + field + '"]') : null; } function parseNum(raw) { if (raw === null || raw === undefined) { return null; } var v = parseFloat(String(raw).replace(",", ".")); return (isFinite(v) && v > 0) ? v : null; } function readFilter() { var f = {}; for (var i = 0; i < FIELDS.length; i++) { var input = getInput(FIELDS[i]); f[FIELDS[i]] = input ? parseNum(input.value) : null; } return f; } function isActive(f) { for (var i = 0; i < FIELDS.length; i++) { if (f[FIELDS[i]] !== null) { return true; } } return false; } function inRange(value, min, max) { if (min === null && max === null) { return true; } if (!(value > 0)) { return false; } if (min !== null && value < min) { return false; } if (max !== null && value > max) { return false; } return true; } function applyFilter() { var f = readFilter(); var active = isActive(f); var cards = document.querySelectorAll("article.cmp-product-thumb"); var total = 0; var visible = 0; for (var i = 0; i < cards.length; i++) { var card = cards[i]; var wrapper = card.closest("li") || card; total++; if (!active) { /* Nur bei tatsaechlicher Aenderung anfassen — vermeidet Repaint-Stuerme, wenn der Filter waehrend der schrittweisen Vue-Hydration mehrfach laeuft (Flackern) */ if (wrapper.style.display !== "") { wrapper.style.display = ""; } visible++; continue; } var dims = card.querySelector(".js-item-dimensions"); var h = dims ? parseFloat(dims.getAttribute("data-height-mm")) : NaN; var w = dims ? parseFloat(dims.getAttribute("data-width-mm")) : NaN; var l = dims ? parseFloat(dims.getAttribute("data-length-mm")) : NaN; var ok = inRange(h, f.heightMin, f.heightMax) && inRange(w, f.widthMin, f.widthMax) && inRange(l, f.lengthMin, f.lengthMax); var desired = ok ? "" : "none"; if (wrapper.style.display !== desired) { wrapper.style.display = desired; } if (ok) { visible++; } } updateStatus(active, visible, total); updateUrl(f, active); syncPaginationLinks(f, active); } function updateStatus(active, visible, total) { var widget = getWidget(); var status = widget ? widget.querySelector(".mf-status") : null; if (!status) { return; } var text = ""; if (active) { var hasMorePages = !!document.querySelector("ul.pagination"); if (!hasMorePages) { /* Alle Artikel der Kategorie sind geladen: ehrliche Gesamtzahl */ text = (visible === 0) ? "Keine passenden Artikel gefunden – bitte Filter lockern." : visible + " von " + total + " Artikeln passen."; } else if (needsFullListReload()) { /* Nachladen steht unmittelbar bevor */ text = visible + " von " + total + " geladenen Artikeln passen – alle Artikel der Kategorie werden geladen …"; } else { /* items ist bereits maximal, Kategorie hat trotzdem mehr Seiten */ text = visible + " von " + total + " geladenen Artikeln passen – weitere auf den Folgeseiten (Filter bleibt beim Blättern aktiv)."; } } /* Nur bei Aenderung schreiben (vermeidet unnoetige Repaints) */ if (status.textContent !== text) { status.textContent = text; } } function updateUrl(f, active) { try { var url = new URL(window.location.href); for (var i = 0; i < FIELDS.length; i++) { if (active && f[FIELDS[i]] !== null) { url.searchParams.set(FIELDS[i], String(f[FIELDS[i]])); } else { url.searchParams.delete(FIELDS[i]); } } window.history.replaceState(window.history.state, document.title, url.toString()); } catch (e) { /* URL-API nicht verfuegbar: Parameter-Sync entfaellt */ } } /* Pagination-Sync: Die Seitenzahl-Links sind serverseitig gerenderte Links mit eingebackenem Query-String. Nach dem Laden eingegebene Filterwerte muessen aktiv in die hrefs geschrieben werden, sonst gehen sie beim Blaettern verloren. */ function syncLinkHref(link, f, active) { try { var url = new URL(link.getAttribute("href"), window.location.href); for (var i = 0; i < FIELDS.length; i++) { if (active && f[FIELDS[i]] !== null) { url.searchParams.set(FIELDS[i], String(f[FIELDS[i]])); } else { url.searchParams.delete(FIELDS[i]); } } link.setAttribute("href", url.toString()); } catch (e) { /* ungueltige URL: Link unveraendert lassen */ } } function syncPaginationLinks(f, active) { var links = document.querySelectorAll("ul.pagination a.page-link[href]"); for (var i = 0; i < links.length; i++) { syncLinkHref(links[i], f, active); } } function prefillFromUrl() { try { var params = new URL(window.location.href).searchParams; for (var i = 0; i < FIELDS.length; i++) { var value = params.get(FIELDS[i]); var input = getInput(FIELDS[i]); if (input && value !== null && parseNum(value) !== null) { input.value = parseNum(value); } } } catch (e) { /* URL-API nicht verfuegbar */ } } function scheduleApply() { if (debounceTimer) { clearTimeout(debounceTimer); } debounceTimer = setTimeout(applyFilter, DEBOUNCE_MS); } /* Auto-Volle-Liste: Bei aktivem Filter und mehrseitiger Kategorie die Seite einmalig mit items=FULL_LIST_ITEMS neu laden, damit Filter und Trefferanzeige alle Artikel der Kategorie sehen. Kein Endlos-Reload: Steht items bereits auf FULL_LIST_ITEMS, wird nicht erneut geladen. */ function needsFullListReload() { try { var params = new URL(window.location.href).searchParams; if (params.get("items") === String(FULL_LIST_ITEMS)) { return false; } return !!document.querySelector("ul.pagination"); } catch (e) { return false; } } function reloadWithFullList() { try { var f = readFilter(); var url = new URL(window.location.href); url.searchParams.set("items", String(FULL_LIST_ITEMS)); url.searchParams.delete("page"); for (var i = 0; i < FIELDS.length; i++) { if (f[FIELDS[i]] !== null) { url.searchParams.set(FIELDS[i], String(f[FIELDS[i]])); } else { url.searchParams.delete(FIELDS[i]); } } window.location.assign(url.toString()); } catch (e) { /* URL-API nicht verfuegbar */ } } function maybeReloadForFullList() { var f = readFilter(); if (isActive(f) && needsFullListReload()) { reloadWithFullList(); } } /* Debouncte Variante: feuert erst nach Tipppause, damit der Reload niemanden mitten in der Eingabe unterbricht. Prüft die Bedingungen erst beim Feuern erneut (Filter könnte inzwischen geleert sein). */ function scheduleAutoReload() { if (reloadTimer) { clearTimeout(reloadTimer); } reloadTimer = setTimeout(maybeReloadForFullList, RELOAD_DEBOUNCE_MS); } /* Sicherheitsnetz: den angeklickten Pagination-Link unmittelbar vor der Navigation mit den aktuellen Filterwerten versorgen (Capture- Phase laeuft vor der Standard-Navigation des Browsers). */ document.addEventListener("click", function (e) { var link = e.target && e.target.closest ? e.target.closest("ul.pagination a.page-link[href]") : null; if (link) { var f = readFilter(); syncLinkHref(link, f, isActive(f)); } }, true); document.addEventListener("change", function (e) { if (e.target && e.target.classList && e.target.classList.contains("mf-input")) { scheduleAutoReload(); } }); document.addEventListener("keydown", function (e) { if (e.key === "Enter" && e.target && e.target.classList && e.target.classList.contains("mf-input")) { e.preventDefault(); applyFilter(); maybeReloadForFullList(); } }); /* Event-Delegation: robust, falls Vue Teile des DOM neu rendert */ document.addEventListener("input", function (e) { if (e.target && e.target.classList && e.target.classList.contains("mf-input")) { scheduleApply(); scheduleAutoReload(); } }); document.addEventListener("click", function (e) { var btn = e.target && e.target.closest ? e.target.closest(".mf-reset") : null; if (btn) { if (reloadTimer) { clearTimeout(reloadTimer); } for (var i = 0; i < FIELDS.length; i++) { var input = getInput(FIELDS[i]); if (input) { input.value = ""; } } applyFilter(); } }); function startObserver() { /* Karten werden per intersect-Komponente lazy hydratisiert: Filter erneut anwenden, wenn neue Karten im DOM auftauchen. Beobachtet nur childList (keine Attribute), unsere style-Aenderungen triggern den Observer daher nicht. */ var list = document.querySelector("ul.product-list") || document.body; if (observer) { observer.disconnect(); } observer = new MutationObserver(function () { var f = readFilter(); if (isActive(f)) { scheduleApply(); } }); observer.observe(list, { childList: true, subtree: true }); } function init() { if (!getWidget()) { return; } prefillFromUrl(); var f = readFilter(); if (isActive(f)) { applyFilter(); /* Ankunft mit Filterparametern in der URL (z. B. vom Startseiten-Formular): volle Liste sofort nachladen */ maybeReloadForFullList(); } startObserver(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();