aboutsummaryrefslogtreecommitdiffstats
path: root/app/main.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-11-08 19:40:00 +0200
committerPetri Hienonen <petri.hienonen@gmail.com>2025-11-08 19:40:00 +0200
commita9a0070662c2494b37528d27d7420f3da33e749d (patch)
treecaf97caf333086fee1fc9684e8743f08004a6842 /app/main.js
parent08528a9e05a12e564f2c3776be4c8ae672f5054c (diff)
downloadhousing-a9a0070662c2494b37528d27d7420f3da33e749d.tar.zst
Try to refactor dom
Diffstat (limited to 'app/main.js')
-rw-r--r--app/main.js492
1 files changed, 216 insertions, 276 deletions
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) {
@@ -466,111 +511,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
*/