From a9a0070662c2494b37528d27d7420f3da33e749d Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sat, 8 Nov 2025 19:40:00 +0200 Subject: Try to refactor dom --- app/main.js | 492 ++++++++++++++++++++++++++---------------------------------- 1 file changed, 216 insertions(+), 276 deletions(-) (limited to 'app/main.js') diff --git a/app/main.js b/app/main.js index 6f5f7b7..b19d624 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,4 @@ -import { Dom } from "dom"; +import { Dom, DomOptions } from "dom"; import { ColorParameter, MapEl } from "map"; import { DataProvider, @@ -80,60 +80,68 @@ export class App { }, }); - this.#stats = Dom.div({ - id: "stats", - styles: { - background: "#fff", - borderTop: "1px solid #ddd", - flexShrink: "0", - fontSize: "0.95rem", - padding: "0.75rem 1rem", - }, - }); - - const loading = Dom.div({ - id: "loading", - styles: { - background: "white", - borderRadius: "8px", - boxShadow: "0 2px 10px rgba(0,0,0,0.1)", - color: "#555", - fontSize: "1.2rem", - left: "50%", - padding: "2rem", - position: "absolute", - textAlign: "center", - top: "50%", - transform: "translate(-50%, -50%)", - zIndex: "1000", - }, - textContent: "Loading data…", - }); + this.#stats = Dom.div( + new DomOptions({ + id: "stats", + styles: { + background: "#fff", + borderTop: "1px solid #ddd", + flexShrink: "0", + fontSize: "0.95rem", + padding: "0.75rem 1rem", + }, + }), + ); - document.body.append( - loading, - Dom.div({ - children: [ - this.#controls, - Dom.div({ - children: [this.#map.svg, this.#stats], - id: "map-container", - styles: { - display: "flex", - flex: "1", - flexDirection: "column", - minWidth: "0", // Prevents flex overflow - }, - }), - ], - id: "main", + const loading = Dom.span( + "Loading data…", + new DomOptions({ + id: "loading", styles: { - display: "flex", - flex: "1", - overflow: "hidden", + background: "white", + borderRadius: "8px", + boxShadow: "0 2px 10px rgba(0,0,0,0.1)", + color: "#555", + fontSize: "1.2rem", + left: "50%", + padding: "2rem", + position: "absolute", + textAlign: "center", + top: "50%", + transform: "translate(-50%, -50%)", + zIndex: "1000", }, }), ); + + document.body.append( + loading, + Dom.div( + new DomOptions({ + children: [ + this.#controls, + Dom.div( + new DomOptions({ + children: [this.#map.svg, this.#stats], + id: "map-container", + styles: { + display: "flex", + flex: "1", + flexDirection: "column", + minWidth: "0", // Prevents flex overflow + }, + }), + ), + ], + id: "main", + styles: { + display: "flex", + flex: "1", + overflow: "hidden", + }, + }), + ), + ); this.#initialize(loading); } @@ -147,60 +155,73 @@ export class App { * @returns {HTMLElement} */ static buildControls(filters, weights, onFilterChange, onWeightChange, onColorChange) { - const controls = Dom.div({ - styles: { - background: "#fff", - borderRight: "1px solid #ddd", - display: "flex", - flexDirection: "column", - flexShrink: "0", - gap: "1rem", - overflowY: "auto", - padding: "1rem", - width: "300px", - }, - }); + const controls = Dom.div( + new DomOptions({ + styles: { + background: "#fff", + borderRight: "1px solid #ddd", + display: "flex", + flexDirection: "column", + flexShrink: "0", + gap: "1rem", + overflowY: "auto", + padding: "1rem", + width: "300px", + }, + }), + ); // Color parameter section - const colorSection = Dom.div({ - styles: { - borderBottom: "1px solid #eee", - paddingBottom: "1rem", - }, - }); + const colorSection = Dom.div( + new DomOptions({ + styles: { + borderBottom: "1px solid #eee", + paddingBottom: "1rem", + }, + }), + ); - const colorTitle = Dom.heading(3, { - styles: { - color: "#333", - fontSize: "1.1rem", - margin: "0 0 1rem 0", - }, - textContent: "Map Colors", - }); + const colorTitle = Dom.heading( + 3, + "Map Colors", + new DomOptions({ + styles: { + color: "#333", + fontSize: "1.1rem", + margin: "0 0 1rem 0", + }, + }), + ); - const colorGroup = Dom.div({ - styles: { display: "flex", flexDirection: "column" }, - }); + const colorGroup = Dom.div( + new DomOptions({ + styles: { display: "flex", flexDirection: "column" }, + }), + ); - const colorLabel = Dom.label({ - for: "color-parameter", - styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, - textContent: "Color houses by", - }); + const colorLabel = Dom.label( + "color-parameter", + "Color houses by", + new DomOptions({ + styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, + }), + ); - const colorSelect = Dom.select({ - id: "color-parameter", - onChange: (e) => { + const colorSelect = Dom.select( + (e) => { const target = /** @type {HTMLSelectElement} */ (e.target); onColorChange(target.value); }, - styles: { - border: "1px solid #ddd", - borderRadius: "4px", - fontSize: "0.9rem", - padding: "0.5rem", - }, - }); + new DomOptions({ + id: "color-parameter", + styles: { + border: "1px solid #ddd", + borderRadius: "4px", + fontSize: "0.9rem", + padding: "0.5rem", + }, + }), + ); colorSelect.append( Dom.option(ColorParameter.price, "Price"), @@ -214,31 +235,38 @@ export class App { controls.appendChild(colorSection); // Filter section - const filterSection = Dom.div({ - styles: { - borderBottom: "1px solid #eee", - paddingBottom: "1rem", - }, - }); + const filterSection = Dom.div( + new DomOptions({ + styles: { + borderBottom: "1px solid #eee", + paddingBottom: "1rem", + }, + }), + ); - const filterTitle = Dom.heading(3, { - styles: { - color: "#333", - fontSize: "1.1rem", - margin: "0 0 1rem 0", - }, - textContent: "Filters", - }); + const filterTitle = Dom.heading( + 3, + "Filters", + new DomOptions({ + styles: { + color: "#333", + fontSize: "1.1rem", + margin: "0 0 1rem 0", + }, + }), + ); filterSection.appendChild(filterTitle); // Price filters in a row - const priceRow = Dom.div({ - styles: { - display: "flex", - gap: "0.5rem", - }, - }); + const priceRow = Dom.div( + new DomOptions({ + styles: { + display: "flex", + gap: "0.5rem", + }, + }), + ); const minPriceFilter = App.addNumberFilter("min-price", "Min price (€)", (v) => { filters.minPrice = v ?? 0; @@ -263,32 +291,39 @@ export class App { }); // District multi-select - const districtGroup = Dom.div({ - styles: { display: "flex", flexDirection: "column" }, - }); + const districtGroup = Dom.div( + new DomOptions({ + styles: { display: "flex", flexDirection: "column" }, + }), + ); - const districtLabel = Dom.label({ - for: "district-select", - styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, - textContent: "Districts", - }); + const districtLabel = Dom.label( + "district-select", + "Districts", + new DomOptions({ + styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, + }), + ); - const districtSelect = Dom.select({ - attributes: { multiple: "true" }, - id: "district-select", - onChange: (e) => { + const districtSelect = Dom.select( + (e) => { const target = /** @type {HTMLSelectElement} */ (e.target); const selectedOptions = Array.from(target.selectedOptions).map((opt) => opt.value); filters.districts = selectedOptions; onFilterChange(); }, - styles: { - border: "1px solid #ddd", - borderRadius: "4px", - minHeight: "120px", - padding: "0.5rem", - }, - }); + + new DomOptions({ + attributes: { multiple: "true" }, + id: "district-select", + styles: { + border: "1px solid #ddd", + borderRadius: "4px", + minHeight: "120px", + padding: "0.5rem", + }, + }), + ); districtGroup.append(districtLabel, districtSelect); @@ -296,21 +331,26 @@ export class App { controls.appendChild(filterSection); // Weights section - const weightsSection = Dom.div({ - styles: { - borderBottom: "1px solid #eee", - paddingBottom: "1rem", - }, - }); + const weightsSection = Dom.div( + new DomOptions({ + styles: { + borderBottom: "1px solid #eee", + paddingBottom: "1rem", + }, + }), + ); - const weightsTitle = Dom.heading(3, { - styles: { - color: "#333", - fontSize: "1.1rem", - margin: "0 0 1rem 0", - }, - textContent: "Weights", - }); + const weightsTitle = Dom.heading( + 3, + "Weights", + new DomOptions({ + styles: { + color: "#333", + fontSize: "1.1rem", + margin: "0 0 1rem 0", + }, + }), + ); weightsSection.appendChild(weightsTitle); @@ -364,32 +404,39 @@ export class App { * @returns {HTMLElement} */ static addNumberFilter(id, labelText, onChange) { - const group = Dom.div({ - styles: { display: "flex", flexDirection: "column", marginBottom: "0.75rem" }, - }); - - const label = Dom.label({ - for: id, - styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, - textContent: labelText, - }); + const group = Dom.div( + new DomOptions({ + styles: { display: "flex", flexDirection: "column", marginBottom: "0.75rem" }, + }), + ); - const input = Dom.input({ + const label = Dom.label( id, - onInput: /** @param {Event} e */ (e) => { + labelText, + new DomOptions({ + styles: { fontSize: "0.85rem", fontWeight: "bold", marginBottom: "0.25rem" }, + }), + ); + + const input = Dom.input( + "number", + (e) => { const target = /** @type {HTMLInputElement} */ (e.target); const raw = target.value.trim(); onChange(raw === "" ? null : Number(raw)); }, - placeholder: "any", - styles: { - border: "1px solid #ddd", - borderRadius: "4px", - fontSize: "0.9rem", - padding: "0.5rem", - }, - type: "number", - }); + "any", + "", + new DomOptions({ + id, + styles: { + border: "1px solid #ddd", + borderRadius: "4px", + fontSize: "0.9rem", + padding: "0.5rem", + }, + }), + ); group.append(label, input); return group; @@ -438,9 +485,7 @@ export class App { } }); - // Build modal content - const content = this.#buildHouseModalContent(house); - this.#modal.appendChild(content); + this.#modal.appendChild(Dom.buildHouseModalContent(house)); document.body.appendChild(this.#modal); if (persistent) { @@ -465,111 +510,6 @@ export class App { } } - /** - * Build modal content for a house - * @param {House} house - * @returns {DocumentFragment} - */ - #buildHouseModalContent(house) { - const frag = document.createDocumentFragment(); - - /* Header */ - const header = Dom.div({ - styles: { - alignItems: "center", - display: "flex", - justifyContent: "space-between", - marginBottom: "20px", - }, - }); - const title = Dom.heading(2, { - styles: { color: "#333", fontSize: "20px", margin: "0" }, - textContent: house.address, - }); - const score = Dom.span({ - styles: { - background: "#e8f5e9", - borderRadius: "4px", - color: "#2e7d32", - fontSize: "16px", - fontWeight: "bold", - padding: "4px 8px", - }, - textContent: `Score: ${house.scores.current}`, - }); - Dom.appendChildren(header, [title, score]); - frag.appendChild(header); - - /* Details grid */ - const grid = Dom.div({ - styles: { - display: "grid", - gap: "15px", - gridTemplateColumns: "repeat(2,1fr)", - marginBottom: "20px", - }, - }); - const details = [ - { label: "Price", value: `€${house.price.toLocaleString()}` }, - { label: "Building Type", value: house.buildingType }, - { label: "Construction Year", value: house.constructionYear?.toString() ?? "N/A" }, - { label: "Living Area", value: `${house.livingArea} m²` }, - { label: "District", value: house.district }, - { label: "Rooms", value: house.rooms?.toString() ?? "N/A" }, - ]; - for (const { label, value } of details) { - const item = Dom.div({ - children: [ - Dom.div({ - styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "4px" }, - textContent: label, - }), - Dom.div({ styles: { color: "#333", fontSize: "14px" }, textContent: value }), - ], - }); - grid.appendChild(item); - } - frag.appendChild(grid); - - /* Description */ - const descSect = Dom.div({ styles: { marginBottom: "20px" } }); - const descTitle = Dom.div({ - styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" }, - textContent: "Description", - }); - const descText = Dom.p({ - styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" }, - textContent: house.description || "No description available.", - }); - Dom.appendChildren(descSect, [descTitle, descText]); - frag.appendChild(descSect); - - /* Images */ - if (house.images?.length) { - const imgSect = Dom.div({ styles: { marginBottom: "20px" } }); - const imgTitle = Dom.div({ - styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" }, - textContent: "Images", - }); - const imgCont = Dom.div({ - styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" }, - }); - for (const src of house.images.slice(0, 3)) { - imgCont.appendChild( - Dom.img({ - attributes: { loading: "lazy" }, - src, - styles: { borderRadius: "4px", flexShrink: "0", height: "100px" }, - }), - ); - } - Dom.appendChildren(imgSect, [imgTitle, imgCont]); - frag.appendChild(imgSect); - } - - return frag; - } - /** * Load data and initialize application * @param {HTMLElement} loading -- cgit v1.2.3-70-g09d2