aboutsummaryrefslogtreecommitdiffstats
path: root/app/dom.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-11-14 16:25:45 +0200
committerPetri Hienonen <petri.hienonen@gmail.com>2025-11-14 16:25:45 +0200
commit55085dae685305d24c29b60b1c16fc7dc76831af (patch)
tree4833cb2974bf8922c804bbb463113f0fe568f7a3 /app/dom.js
parenta92ad2b817b2c28b26e869897e03c14d30d0f991 (diff)
downloadhousing-55085dae685305d24c29b60b1c16fc7dc76831af.tar.zst
Add slider filters and initialisation screen that handles data loading before application starts
Diffstat (limited to 'app/dom.js')
-rw-r--r--app/dom.js192
1 files changed, 191 insertions, 1 deletions
diff --git a/app/dom.js b/app/dom.js
index 3ee393c..9ea7bf9 100644
--- a/app/dom.js
+++ b/app/dom.js
@@ -125,7 +125,7 @@ export class Dom {
* @param {DomOptions} o
* @param {string|undefined} text
*/
- static a(url, o, text) {
+ static a(url, o, text = undefined) {
const link = document.createElement("a");
if (text) link.text = text;
link.href = url;
@@ -238,4 +238,194 @@ export class Dom {
if (o.children) p.append(...o.children);
return p;
}
+
+ /**
+ * Create a dual range input (min and max)
+ * @param {number} min - Minimum possible value
+ * @param {number} max - Maximum possible value
+ * @param {number} currentMin - Current minimum value
+ * @param {number} currentMax - Current maximum value
+ * @param {number} step - Step size
+ * @param {(min: number, max: number) => void} onChange - Callback when range changes
+ * @param {DomOptions} options - DOM options
+ * @returns {HTMLDivElement}
+ */
+ static range(min, max, currentMin, currentMax, step, onChange, options = new DomOptions()) {
+ const container = document.createElement("div");
+ Object.assign(container.style, options.styles);
+ if (options.id) container.id = options.id;
+ for (const cls of options.classes) container.classList.add(cls);
+ for (const [k, v] of Object.entries(options.attributes)) container.setAttribute(k, v);
+
+ // Ensure current values are within bounds
+ const safeCurrentMin = Math.max(min, Math.min(currentMin, max));
+ const safeCurrentMax = Math.max(min, Math.min(currentMax, max));
+
+ // Create track container
+ const trackContainer = Dom.div(
+ new DomOptions({
+ styles: {
+ alignItems: "center",
+ display: "flex",
+ height: "20px",
+ margin: "10px 0",
+ position: "relative",
+ },
+ }),
+ );
+
+ // Create track
+ const track = Dom.div(
+ new DomOptions({
+ styles: {
+ background: "#e0e0e0",
+ borderRadius: "4px",
+ height: "6px",
+ position: "relative",
+ width: "100%",
+ },
+ }),
+ );
+
+ // Create active range
+ const activeRange = Dom.div(
+ new DomOptions({
+ styles: {
+ background: "#4caf50",
+ borderRadius: "4px",
+ height: "100%",
+ left: "0%",
+ position: "absolute",
+ width: "100%",
+ },
+ }),
+ );
+
+ // Create min slider
+ const minSlider = document.createElement("input");
+ minSlider.type = "range";
+ minSlider.min = min.toString();
+ minSlider.max = max.toString();
+ minSlider.step = step.toString();
+ minSlider.value = safeCurrentMin.toString();
+ Object.assign(minSlider.style, {
+ appearance: "none",
+ background: "transparent",
+ height: "100%",
+ pointerEvents: "none",
+ position: "absolute",
+ width: "100%",
+ zIndex: "2",
+ });
+ minSlider.style.pointerEvents = "auto";
+
+ // Create max slider
+ const maxSlider = document.createElement("input");
+ maxSlider.type = "range";
+ maxSlider.min = min.toString();
+ maxSlider.max = max.toString();
+ maxSlider.step = step.toString();
+ maxSlider.value = safeCurrentMax.toString();
+ Object.assign(maxSlider.style, {
+ appearance: "none",
+ background: "transparent",
+ height: "100%",
+ pointerEvents: "none",
+ position: "absolute",
+ width: "100%",
+ zIndex: "2",
+ });
+ maxSlider.style.pointerEvents = "auto";
+
+ // Value displays
+ const minValueDisplay = Dom.span(
+ safeCurrentMin.toString(),
+ new DomOptions({
+ styles: {
+ color: "#0066cc",
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ },
+ }),
+ );
+
+ const maxValueDisplay = Dom.span(
+ safeCurrentMax.toString(),
+ new DomOptions({
+ styles: {
+ color: "#0066cc",
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ },
+ }),
+ );
+
+ const valueDisplay = Dom.div(
+ new DomOptions({
+ children: [minValueDisplay, maxValueDisplay],
+ styles: {
+ display: "flex",
+ justifyContent: "space-between",
+ marginBottom: "5px",
+ },
+ }),
+ );
+
+ // Update active range position
+ const updateActiveRange = () => {
+ const minVal = parseInt(minSlider.value);
+ const maxVal = parseInt(maxSlider.value);
+
+ const minPercent = ((minVal - min) / (max - min)) * 100;
+ const maxPercent = ((maxVal - min) / (max - min)) * 100;
+
+ activeRange.style.left = `${minPercent}%`;
+ activeRange.style.width = `${maxPercent - minPercent}%`;
+
+ minValueDisplay.textContent = minVal.toString();
+ maxValueDisplay.textContent = maxVal.toString();
+ };
+
+ // Event listeners
+ minSlider.addEventListener("input", () => {
+ const minVal = parseInt(minSlider.value);
+ const maxVal = parseInt(maxSlider.value);
+
+ if (minVal > maxVal) {
+ minSlider.value = maxVal.toString();
+ }
+
+ updateActiveRange();
+ onChange(parseInt(minSlider.value), parseInt(maxSlider.value));
+ });
+
+ maxSlider.addEventListener("input", () => {
+ const minVal = parseInt(minSlider.value);
+ const maxVal = parseInt(maxSlider.value);
+
+ if (maxVal < minVal) {
+ maxSlider.value = minVal.toString();
+ }
+
+ updateActiveRange();
+ onChange(parseInt(minSlider.value), parseInt(maxSlider.value));
+ });
+
+ // Initial update
+ updateActiveRange();
+
+ // Assemble track
+ track.appendChild(activeRange);
+ track.appendChild(minSlider);
+ track.appendChild(maxSlider);
+ trackContainer.appendChild(track);
+
+ // Assemble container
+ container.appendChild(valueDisplay);
+ container.appendChild(trackContainer);
+
+ if (options.children) container.append(...options.children);
+
+ return container;
+ }
}