Files
pbt/static/stops.js
T
zisco 2a6e9c690b
Deploy PBT / deploy (push) Successful in 16s
Fetch stops from OpenStreetMap
2026-09-10 21:44:45 +02:00

145 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Autocomplete for the origin/destination fields, backed by /api/stops.
Progressive enhancement: without JS the inputs stay plain free-text fields.
A picked stop fills the sibling hidden <input> with its id; editing the text
afterwards clears it again, so unmatched entries save as free text. */
(function () {
"use strict";
var TYPE_LABEL = {
bus: "Bus",
tram: "Straßenbahn",
subway: "U-Bahn",
train: "Zug",
bus_station: "Busbahnhof",
other: "Haltestelle",
};
function debounce(fn, ms) {
var t;
return function () {
var args = arguments, self = this;
clearTimeout(t);
t = setTimeout(function () { fn.apply(self, args); }, ms);
};
}
function setup(input) {
var wrap = input.closest(".combo");
var hidden = wrap.querySelector(input.dataset.stopTarget);
var hint = wrap.querySelector("[data-stop-hint]");
if (!hidden) return;
var list = document.createElement("div");
list.className = "combo-list";
list.hidden = true;
wrap.appendChild(list);
var items = [];
var active = -1;
var lastQuery = "";
function clearMatch() {
hidden.value = "";
if (hint) { hint.textContent = ""; hint.classList.remove("matched"); }
}
function markMatch(stop) {
hidden.value = String(stop.id);
if (hint) {
hint.textContent =
"✓ " + stop.name + (stop.municipality ? ", " + stop.municipality : "");
hint.classList.add("matched");
}
}
function close() { list.hidden = true; active = -1; }
function render() {
list.innerHTML = "";
if (!items.length) {
var empty = document.createElement("div");
empty.className = "combo-empty";
empty.textContent =
"Keine Haltestelle gefunden Eingabe wird als Freitext gespeichert.";
list.appendChild(empty);
list.hidden = false;
return;
}
items.forEach(function (stop, i) {
var opt = document.createElement("div");
opt.className = "combo-option" + (i === active ? " active" : "");
var name = document.createElement("span");
name.className = "name";
name.textContent = stop.name;
opt.appendChild(name);
if (stop.municipality) {
var muni = document.createElement("span");
muni.className = "muni";
muni.textContent = stop.municipality;
opt.appendChild(muni);
}
var kind = document.createElement("span");
kind.className = "kind";
kind.textContent = TYPE_LABEL[stop.type] || "";
opt.appendChild(kind);
opt.addEventListener("mousedown", function (e) {
e.preventDefault();
choose(i);
});
list.appendChild(opt);
});
list.hidden = false;
}
function choose(i) {
var stop = items[i];
if (!stop) return;
input.value = stop.name;
markMatch(stop);
close();
}
var search = debounce(function () {
var q = input.value.trim();
lastQuery = q;
if (q.length < 2) { items = []; close(); return; }
fetch("/api/stops?q=" + encodeURIComponent(q))
.then(function (r) { return r.ok ? r.json() : []; })
.then(function (data) {
if (input.value.trim() !== lastQuery) return;
items = data;
active = -1;
render();
var exact = data.filter(function (s) {
return s.name.toLowerCase() === lastQuery.toLowerCase();
})[0];
if (exact) { markMatch(exact); } else { clearMatch(); }
})
.catch(function () { items = []; close(); });
}, 180);
input.setAttribute("autocomplete", "off");
input.addEventListener("input", function () { clearMatch(); search(); });
input.addEventListener("focus", function () { if (items.length) render(); });
input.addEventListener("blur", function () { setTimeout(close, 120); });
input.addEventListener("keydown", function (e) {
if (list.hidden) return;
if (e.key === "ArrowDown") {
e.preventDefault();
active = Math.min(active + 1, items.length - 1);
render();
} else if (e.key === "ArrowUp") {
e.preventDefault();
active = Math.max(active - 1, 0);
render();
} else if (e.key === "Enter") {
if (active >= 0) { e.preventDefault(); choose(active); }
} else if (e.key === "Escape") {
close();
}
});
}
document.querySelectorAll("[data-stop-input]").forEach(setup);
})();