Add address autocomplete

This commit is contained in:
Marcus Kida 2026-02-08 14:20:41 +01:00
parent f4d5f282f3
commit a12410d378
3 changed files with 164 additions and 4 deletions

View file

@ -28,6 +28,113 @@
clearBtn.style.display = "none"; clearBtn.style.display = "none";
// === Autocomplete ===
function setupAutocomplete(input, listEl) {
var acTimer = null;
var acController = null;
var activeIdx = -1;
function closeList() {
listEl.classList.remove("open");
listEl.innerHTML = "";
activeIdx = -1;
}
function selectItem(displayName) {
input.value = displayName;
closeList();
}
function updateActive() {
var items = listEl.querySelectorAll("li");
items.forEach(function (li, i) {
li.classList.toggle("active", i === activeIdx);
});
if (activeIdx >= 0 && items[activeIdx]) {
items[activeIdx].scrollIntoView({ block: "nearest" });
}
}
input.addEventListener("input", function () {
clearTimeout(acTimer);
if (acController) acController.abort();
var q = input.value.trim();
if (q.length < 3) {
closeList();
return;
}
acTimer = setTimeout(function () {
acController = new AbortController();
fetch(
"https://nominatim.openstreetmap.org/search?" +
new URLSearchParams({
q: q,
format: "json",
limit: "5",
addressdetails: "0",
}),
{ signal: acController.signal }
)
.then(function (r) { return r.json(); })
.then(function (results) {
listEl.innerHTML = "";
activeIdx = -1;
if (!results.length) {
closeList();
return;
}
results.forEach(function (item) {
var li = document.createElement("li");
var parts = item.display_name.split(", ");
var main = parts.slice(0, 2).join(", ");
var sub = parts.slice(2).join(", ");
li.innerHTML =
'<span class="ac-main">' + escapeHtml(main) + "</span>" +
(sub ? '<br><span class="ac-sub">' + escapeHtml(sub) + "</span>" : "");
li.addEventListener("mousedown", function (e) {
e.preventDefault();
selectItem(item.display_name);
});
listEl.appendChild(li);
});
listEl.classList.add("open");
})
.catch(function (err) {
if (err.name !== "AbortError") closeList();
});
}, 300);
});
input.addEventListener("keydown", function (e) {
if (!listEl.classList.contains("open")) return;
var items = listEl.querySelectorAll("li");
if (e.key === "ArrowDown") {
e.preventDefault();
activeIdx = Math.min(activeIdx + 1, items.length - 1);
updateActive();
} else if (e.key === "ArrowUp") {
e.preventDefault();
activeIdx = Math.max(activeIdx - 1, 0);
updateActive();
} else if (e.key === "Enter" && activeIdx >= 0) {
e.preventDefault();
e.stopPropagation();
var parts = items[activeIdx].querySelector(".ac-main").textContent;
var sub = items[activeIdx].querySelector(".ac-sub");
selectItem(parts + (sub ? ", " + sub.textContent : ""));
} else if (e.key === "Escape") {
closeList();
}
});
input.addEventListener("blur", function () {
setTimeout(closeList, 150);
});
}
setupAutocomplete(fromInput, document.getElementById("ac-from"));
setupAutocomplete(toInput, document.getElementById("ac-to"));
// === Utilities === // === Utilities ===
function getSelectedBand() { function getSelectedBand() {
var has2m = band2m.checked; var has2m = band2m.checked;

View file

@ -16,11 +16,13 @@
<label class="band-label band-70cm"><input type="checkbox" id="band-70cm" checked> 70cm</label> <label class="band-label band-70cm"><input type="checkbox" id="band-70cm" checked> 70cm</label>
<span id="count" class="count"></span> <span id="count" class="count"></span>
</div> </div>
<div class="controls-row route-row"> <div class="controls-row route-row autocomplete-wrap">
<input type="text" id="route-from" placeholder="From address" /> <input type="text" id="route-from" placeholder="From address" autocomplete="off" />
<ul class="autocomplete-list" id="ac-from"></ul>
</div> </div>
<div class="controls-row route-row"> <div class="controls-row route-row autocomplete-wrap">
<input type="text" id="route-to" placeholder="To address" /> <input type="text" id="route-to" placeholder="To address" autocomplete="off" />
<ul class="autocomplete-list" id="ac-to"></ul>
</div> </div>
<div class="controls-row route-row"> <div class="controls-row route-row">
<button id="route-btn">Route</button> <button id="route-btn">Route</button>

View file

@ -69,6 +69,57 @@ html, body {
margin-top: 6px; margin-top: 6px;
} }
.autocomplete-wrap {
position: relative;
}
.autocomplete-list {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1001;
background: white;
border: 1px solid #ccc;
border-top: none;
border-radius: 0 0 5px 5px;
list-style: none;
max-height: 180px;
overflow-y: auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.autocomplete-list.open {
display: block;
}
.autocomplete-list li {
padding: 7px 10px;
font-size: 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
line-height: 1.3;
}
.autocomplete-list li:last-child {
border-bottom: none;
}
.autocomplete-list li:hover,
.autocomplete-list li.active {
background: #f5f5f5;
}
.autocomplete-list li .ac-main {
color: #333;
}
.autocomplete-list li .ac-sub {
color: #999;
font-size: 11px;
}
.route-row input[type="text"] { .route-row input[type="text"] {
width: 100%; width: 100%;
padding: 7px 10px; padding: 7px 10px;