Hjemmebygget DAWA-autocomplete med fetch eller jQuery
Flyt et selvskrevet adressefelt fra DAWA's /autocomplete til Danadresse – URL, nøgle, JSONP eller JSON, fejlhåndtering og caretpos-løkken.
Mange byggede deres eget adressefelt oven på DAWA's /autocomplete med fetch, XMLHttpRequest eller jQuery. Den slags kode virker igen med få ændringer, fordi svaret har samme form: en liste af forslag med type, tekst, forslagstekst, caretpos og data.
Guiden gennemgår de fem ting, du skal ændre, og viser et komplet eksempel.
1. Skift URL'en og tilføj en browsernøgle
Typisk DAWA-kode så sådan ud:
$("#adresse").on("input", function () { $.ajax({ url: "https://dawa.aws.dk/autocomplete", dataType: "jsonp", data: { q: this.value, caretpos: this.selectionStart, type: "adresse" }, success: visForslag }); });
Domænet bliver https://api.danadresse.dk, og kaldet skal have en nøgle. Koden kører i browseren, så nøglen er synlig for alle. Brug en nøgle, der er låst til dine domæner under Tilladte domæner i Nøgler – se Browsernøgler og tilladte domæner.
Send nøglen som api_key i URL'en. En X-Api-Key-header virker også, men en egen header får browseren til at sende en ekstra CORS-forespørgsel (OPTIONS) før kaldet, og det gør den for hver ny URL – altså for hvert tastetryk. Med api_key i URL'en slipper du for den ekstra rundtur.
2. Brug hellere JSON end JSONP
JSONP virker som i DAWA: med callback=fn bliver svaret pakket ind som fn(…) (application/javascript), så jQuery's dataType: "jsonp" virker videre, når nøglen er tilføjet. Men med JSONP kører browseren ikke scriptet, når kaldet fejler, så din kode ser aldrig status eller fejlens code. CORS er åbent for alle domæner, så skift til dataType: "json" eller fetch, når du alligevel retter i koden.
3. Håndter fejl
DAWA svarede næsten altid 200. Hos os kan et kald fejle af grunde, der handler om nøglen og forbruget. Fejlsvar på DAWA-stierne har DAWA's form {"type", "title", "details", "code"}. Styr efter code:
| Status | code | Hvad du gør |
|---|---|---|
| 401 | missing_api_key, invalid_api_key | Vis ingen forslag og log fejlen. Det er en konfigurationsfejl, så prøv ikke igen |
| 403 | origin_not_allowed | Sidens domæne mangler på nøglens liste. Prøv ikke igen |
| 403 | key_blocked | Nøglen er spærret som lækket. Lås den til dine domæner eller lav en ny |
| 429 | rate_limit_per_visitor, rate_limit_per_minute | Hold pause et par sekunder |
| 429 | quota_exceeded | Månedskvoten på gratisplanen er brugt. Prøv ikke igen før næste måned |
De minutbaserede 429-svar har Retry-After med antal sekunder, du skal vente (1 til 60), og browseren lader dit script læse den. Hold pause så længe. quota_exceeded har ingen Retry-After; hold op med at spørge, til brugeren genindlæser siden. Et adressefelt, der holder op med at foreslå et øjeblik, er bedre end et, der bliver ved med at ramme grænsen.
4. Forstå caretpos-løkken
DAWA's autocomplete arbejder i tre trin: vejnavn, adgangsadresse (husnummer) og adresse (etage og dør). Når brugeren vælger et forslag, der ikke er det endelige niveau, skal du:
- sætte feltets tekst til forslagets
tekst, - sætte markøren på
caretpos, - spørge igen med den nye tekst og
caretpos.
Det fortsætter, til brugeren vælger et forslag af den type, du vil ende med. For et adgangsadresse-forslag er tekst fx "Vesterbrogade 145, , 1620 København V" med markøren lige efter "145, ", så brugeren kan skrive etage og dør i det tomme felt. Brug forslagstekst til at vise forslaget i listen og tekst til feltet.
Én forskel fra DAWA: sender du type=adresse, returnerer vi kun forslag på adresseniveau, også mens brugeren stadig skriver vejnavnet. DAWA viste vejnavne først. Uden type får du de tre trin som i DAWA. Skriv løkken, så den virker i begge tilfælde: vælger brugeren et forslag med den ønskede type, er du færdig. Filtrene postnr, kommunekode og adgangsadresseid indsnævrer forslagene som i DAWA, og tolerancen over for stavefejl er et flag som i DAWA: slået til med fuzzy (eller fuzzy=), slået fra uden den eller med fuzzy=false.
Løkken kan du prøve fra Node. Den starter med "Rådhuspladsen 1", vælger første forslag og borer videre, indtil den rammer en adresse:
const API = "https://api.danadresse.dk/autocomplete"; const headers = { "X-Api-Key": process.env.DANADRESSE_API_KEY }; let tekst = "Rådhuspladsen 1"; let caretpos = tekst.length; for (let trin = 1; trin <= 3; trin++) { const url = new URL(API); url.search = new URLSearchParams({ q: tekst, caretpos: String(caretpos), per_side: "5" }); const res = await fetch(url, { headers }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); const forslag = await res.json(); if (forslag.length === 0) break; const valgt = forslag[0]; console.log(`trin ${trin}: ${valgt.type} -> "${valgt.tekst}" (caretpos ${valgt.caretpos})`); if (valgt.type === "adresse") { console.log(`færdig, adresse-id ${valgt.data.id}`); break; } tekst = valgt.tekst; caretpos = valgt.caretpos; }
5. Et komplet felt med fetch
Eksemplet venter 200 ms efter sidste tastetryk (debounce), afbryder et kald, der er blevet forældet, og håndterer fejlene fra trin 3. Det bruger kun browserens egne API'er:
const API = "https://api.danadresse.dk/autocomplete"; const API_KEY = "dawa_live_din_browsernøgle"; // domænelåst browsernøgle const MAAL = "adresse"; // eller "adgangsadresse", hvis du ikke vil have etage og dør const input = document.querySelector("#adresse"); const liste = document.querySelector("#forslag"); let controller = null; let timer = null; let pauseTil = 0; async function hentForslag(tekst, caretpos) { if (Date.now() < pauseTil || tekst.trim().length < 2) return []; controller?.abort(); controller = new AbortController(); const url = new URL(API); url.search = new URLSearchParams({ q: tekst, caretpos: String(caretpos), per_side: "10", api_key: API_KEY, }); let res; try { res = await fetch(url, { signal: controller.signal }); } catch (err) { if (err.name === "AbortError") return null; // et nyere tastetryk har overtaget throw err; } if (res.status === 429) { const fejl = await res.json(); // quota_exceeded: måneden er brugt op, stop. Ellers vent Retry-After sekunder. const vent = fejl.code === "quota_exceeded" ? 3600 : Number(res.headers.get("Retry-After")) || 5; pauseTil = Date.now() + vent * 1000; return []; } if (res.status === 401 || res.status === 403) { const fejl = await res.json(); console.error("Danadresse:", fejl.code, fejl.title); return []; } if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } function visForslag(forslag) { liste.replaceChildren(...forslag.map((f) => { const li = document.createElement("li"); li.textContent = f.forslagstekst; li.addEventListener("mousedown", (e) => { e.preventDefault(); vaelg(f); }); return li; })); } async function opdater(tekst, caretpos) { const forslag = await hentForslag(tekst, caretpos); if (forslag) visForslag(forslag); } function vaelg(f) { input.value = f.tekst; input.setSelectionRange(f.caretpos, f.caretpos); if (f.type === MAAL) { liste.replaceChildren(); input.dataset.adresseId = f.data.id; // gem id'et, ikke kun teksten return; } opdater(f.tekst, f.caretpos); // vejnavn eller adgangsadresse: bor videre } input.addEventListener("input", () => { delete input.dataset.adresseId; clearTimeout(timer); timer = setTimeout(() => opdater(input.value, input.selectionStart), 200); });
Med jQuery er ændringen mindre. Skift URL, dataType og tilføj nøglen og en fejlhandler:
$("#adresse").on("input", function () { $.ajax({ url: "https://api.danadresse.dk/autocomplete", dataType: "json", data: { q: this.value, caretpos: this.selectionStart, api_key: API_KEY }, success: visForslag, error: function (xhr) { var fejl = xhr.responseJSON || {}; console.error("Danadresse:", xhr.status, fejl.code); } }); });
Husk også debounce her. Uden den bruger hvert tastetryk et kald af din kvote.
Næste skridt
- Vil du hellere bruge en færdig komponent? Autocomplete-widget i ren JavaScript.
- Hvad betyder grænserne i praksis? Grænser, kvoter og 429.
- Gem det valgte id, ikke kun teksten: Gem adresse-id'et.
Relaterede guides
Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)