Files
photoanalyzer/frontend/js/dom.js
domverse f1442527a2
Some checks failed
Test / suites (push) Failing after 2m56s
Test / container (push) Failing after 5m27s
US09-04: Write the User Manual with Generated Screenshots (#110)
2026-08-23 23:37:31 +02:00

43 lines
1.8 KiB
JavaScript

// Shared DOM helpers used by every view. `el` builds nodes without innerHTML
// injection; the rest are the small conveniences the views repeat.
export function el(tag, attrs = {}, ...children) {
const node = document.createElement(tag);
for (const [key, value] of Object.entries(attrs)) {
if (value == null || value === false) continue;
if (key === "class") node.className = value;
else if (key === "dataset") Object.assign(node.dataset, value);
else if (key.startsWith("on") && typeof value === "function")
node.addEventListener(key.slice(2).toLowerCase(), value);
else node.setAttribute(key, value);
}
for (const child of children.flat()) {
if (child == null || child === false) continue;
node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
}
return node;
}
export function errorBanner(message) {
return el("div", { class: "alert", role: "alert" }, message);
}
/**
* Replace a container's contents, dropping the absent ones.
*
* `el` already ignores null children, but `replaceChildren` does not: it stringifies
* them, so an optional node that is not there renders the word "null" on the page.
* Every view builds optional nodes — a banner only while a job runs, a conflict only
* after a 409 — so the filter belongs here rather than in each caller. It was missing
* from one of them, and a documentation screenshot is where that turned up (US09-04).
*/
export function show(root, ...nodes) {
root.replaceChildren(...nodes.flat().filter((node) => node != null && node !== false));
}
export function setActiveNav(view) {
document.querySelectorAll("nav a[data-nav]").forEach((a) => {
if (a.dataset.nav === view) a.setAttribute("aria-current", "page");
else a.removeAttribute("aria-current");
});
}