From d2443e41d05be9c8b99d66e1780d7ac21a83961a Mon Sep 17 00:00:00 2001 From: Ben de Roo Date: Wed, 24 Jun 2026 21:12:41 +0200 Subject: [PATCH] Add domme-converters/assets/js/components.js --- domme-converters/assets/js/components.js | 187 +++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 domme-converters/assets/js/components.js diff --git a/domme-converters/assets/js/components.js b/domme-converters/assets/js/components.js new file mode 100644 index 0000000..a3be363 --- /dev/null +++ b/domme-converters/assets/js/components.js @@ -0,0 +1,187 @@ +"use strict"; + +/** + * Herbruikbare helpers en UI-componenten. Alles wordt onder de globale + * CL-namespace gehangen zodat router en pagina's het kunnen gebruiken. + */ +(function () { + const CL = (window.CL = window.CL || {}); + + // ---- Opmaak-helpers ------------------------------------------------------- + const formatPrice = (value) => "\u20AC" + Number(value).toFixed(2).replace(".", ","); + const escapeHtml = (str) => + String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); + const stars = (rating) => "\u2605".repeat(rating) + "\u2606".repeat(5 - rating); + + // ---- Navigatiestructuur --------------------------------------------------- + // Eén bron voor de hoofdnavigatie; de footer vult dit aan met extra links. + const NAV = [ + { path: "/producten", label: "Producten" }, + { path: "/catalogus", label: "Catalogus" }, + { path: "/specificaties", label: "Specificaties" }, + { path: "/aanbiedingen", label: "Aanbiedingen" }, + { path: "/bedrijf", label: "Bedrijf" }, + { path: "/contact", label: "Contact" }, + ]; + + const FOOTER_LINKS = { + Producten: [ + { path: "/producten", label: "Producten" }, + { path: "/catalogus", label: "Catalogus" }, + { path: "/specificaties", label: "Specificaties" }, + { path: "/aanbiedingen", label: "Aanbiedingen" }, + ], + Bedrijf: [ + { path: "/bedrijf", label: "Bedrijf" }, + { path: "/over-ons", label: "Over ons" }, + { path: "/vacatures", label: "Vacatures" }, + { path: "/contact", label: "Contact" }, + ], + Juridisch: [ + { path: "/voorwaarden", label: "Voorwaarden" }, + { path: "/privacy", label: "Privacy" }, + { path: "/disclaimer", label: "Disclaimer" }, + ], + }; + + // Bouwt een interne hash-link op die door de router wordt afgevangen. + const href = (path) => "#" + path; + + // ---- Product-thumbnail ---------------------------------------------------- + // Toont de foto als die er is, valt anders terug op de connector-tegel. + function productThumb(p) { + const fallback = + `${escapeHtml(p.from)}` + + `` + + `${escapeHtml(p.to)}`; + if (p.image) { + return `${escapeHtml(p.name)}`; + } + return fallback; + } + + // ---- Productkaart (volledig klikbaar) ------------------------------------ + function productCard(p) { + const fallback = + `${escapeHtml(p.from)}` + + `` + + `${escapeHtml(p.to)}`; + return ` +
+ +
+ ${productThumb(p)} +
+
+
+ ${escapeHtml(p.id)} + ${stars(p.rating)}(${p.reviews}) +
+

${escapeHtml(p.name)}

+

${escapeHtml(p.desc)}

+
+
+
+ ${formatPrice(p.price)}${formatPrice(p.old)} + +
+
+ `; + } + + function productGrid(list) { + if (!list.length) { + return `
Geen producten gevonden.
`; + } + return `
${list.map(productCard).join("")}
`; + } + + // ---- Paginakop ------------------------------------------------------------ + function pageHead({ eyebrow, title, intro, crumb }) { + return ` +
+
+ ${crumb ? `` : ""} + ${eyebrow ? `${escapeHtml(eyebrow)}` : ""} +

${escapeHtml(title)}

+ ${intro ? `

${escapeHtml(intro)}

` : ""} +
+
+ `; + } + + function crumb(parts) { + // parts: [{label, path?}] — laatste item is de huidige pagina. + return parts + .map((part, i) => { + const last = i === parts.length - 1; + const sep = last ? "" : `/`; + if (last || !part.path) return `${escapeHtml(part.label)}${sep}`; + return `${escapeHtml(part.label)}${sep}`; + }) + .join(""); + } + + // ---- Header --------------------------------------------------------------- + function header(activePath) { + const isActive = (path) => + path === activePath || (path !== "/" && activePath.startsWith(path + "/")) ? " active" : ""; + const links = NAV.map( + (item) => `${escapeHtml(item.label)}` + ).join(""); + return ` +
+ +
+ `; + } + + // ---- Footer --------------------------------------------------------------- + function footer() { + const column = (heading, items) => ` +
+

${escapeHtml(heading)}

+ +
`; + return ` + + `; + } + + CL.fmt = { formatPrice, escapeHtml, stars }; + CL.nav = { NAV, FOOTER_LINKS, href }; + CL.components = { productThumb, productCard, productGrid, pageHead, crumb, header, footer }; +})();