Erste Produktivversion
This commit is contained in:
104
web/html/js/dom.js
Normal file
104
web/html/js/dom.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// Kleine DOM-Hilfen. Ersetzt kein Framework, spart nur Wiederholung.
|
||||
"use strict";
|
||||
|
||||
/** el("button.primary", { onclick: fn }, "Text")
|
||||
* Setzt Text ausschließlich über textContent - dadurch kann kein
|
||||
* Benutzereingabewert als HTML interpretiert werden. */
|
||||
export function el(spec, props = {}, ...children) {
|
||||
const [tag, ...classes] = String(spec).split(".");
|
||||
const node = document.createElement(tag || "div");
|
||||
if (classes.length) node.className = classes.join(" ");
|
||||
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (value === null || value === undefined || value === false) continue;
|
||||
if (key.startsWith("on") && typeof value === "function") {
|
||||
node.addEventListener(key.slice(2).toLowerCase(), value);
|
||||
} else if (key === "dataset") {
|
||||
Object.assign(node.dataset, value);
|
||||
} else if (key in node && key !== "list") {
|
||||
node[key] = value;
|
||||
} else {
|
||||
node.setAttribute(key, value === true ? "" : value);
|
||||
}
|
||||
}
|
||||
|
||||
append(node, children);
|
||||
return node;
|
||||
}
|
||||
|
||||
function append(node, children) {
|
||||
for (const child of children.flat(Infinity)) {
|
||||
if (child === null || child === undefined || child === false) continue;
|
||||
node.append(child instanceof Node ? child : document.createTextNode(String(child)));
|
||||
}
|
||||
}
|
||||
|
||||
export function clear(node) {
|
||||
node.replaceChildren();
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Ersetzt den Inhalt eines Knotens.
|
||||
*
|
||||
* Nicht clear(root).append(...) verwenden: Node.append() macht aus einem
|
||||
* null die Zeichenkette "null" und zeigt sie an. Genau dafür filtert
|
||||
* el() seine Kinder - mount() zieht denselben Schutz auf die Wurzel. */
|
||||
export function mount(node, ...children) {
|
||||
node.replaceChildren();
|
||||
for (const child of children.flat(Infinity)) {
|
||||
if (child === null || child === undefined || child === false) continue;
|
||||
node.append(child instanceof Node ? child : document.createTextNode(String(child)));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Preis in Cent -> "1,29 €". Rechnen immer in Cent, formatieren erst hier. */
|
||||
export function euro(cents) {
|
||||
return new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format((cents || 0) / 100);
|
||||
}
|
||||
|
||||
/** "1,29" oder "1.29" -> 129. Gibt null zurück, wenn nichts Sinnvolles drinsteht. */
|
||||
export function parseCents(text) {
|
||||
const cleaned = String(text).trim().replace(",", ".");
|
||||
if (!cleaned) return null;
|
||||
const value = Number(cleaned);
|
||||
if (!Number.isFinite(value) || value < 0) return null;
|
||||
return Math.round(value * 100);
|
||||
}
|
||||
|
||||
export function parseQuantity(text) {
|
||||
const cleaned = String(text).trim().replace(",", ".");
|
||||
if (!cleaned) return null;
|
||||
const value = Number(cleaned);
|
||||
return Number.isFinite(value) && value >= 0 ? value : null;
|
||||
}
|
||||
|
||||
/** Stückzahl: ganze Zahl ab 1. Leer oder unsinnig ergibt 1 - ein Eintrag
|
||||
* ohne Stückzahl wäre unvollständig. */
|
||||
export function parseCount(text) {
|
||||
const value = Number(String(text).trim().replace(",", "."));
|
||||
if (!Number.isFinite(value) || value < 1) return 1;
|
||||
return Math.min(999, Math.round(value));
|
||||
}
|
||||
|
||||
/** Nur das Gebinde, ohne Stückzahl.
|
||||
*
|
||||
* Wird in der Listenzeile gebraucht, weil die Stückzahl dort als
|
||||
* eigenes Feld links neben dem Namen steht - beim Einkaufen soll sie
|
||||
* ins Auge springen und nicht im Kleingedruckten stehen. */
|
||||
export function formatPack(packSize, packUnit) {
|
||||
if (!packSize) return "";
|
||||
return `${formatQuantity(packSize)}${packUnit ? " " + packUnit : ""}`;
|
||||
}
|
||||
|
||||
/** Menge aus der API kommt als Zeichenkette ("2.000"), damit unterwegs
|
||||
* keine Nachkommastellen verloren gehen. Für die Anzeige aufräumen. */
|
||||
export function formatQuantity(value) {
|
||||
if (value === null || value === undefined) return "";
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) return String(value);
|
||||
return num.toLocaleString("de-DE", { maximumFractionDigits: 3 });
|
||||
}
|
||||
Reference in New Issue
Block a user