# Adressefelt til checkout og formularer

Ét script-tag gør et almindeligt inputfelt til et adressefelt med forslag fra det officielle adresseregister: kunden skriver, vælger adressen og eventuelt etage og dør, og postnummer, by og BFE-nummer lander i formularens egne felter. Feltet kalder Grundfast direkte fra browseren med en offentlig nøgle, så du skal ikke bygge en proxy på din server.

_Prøv adressefeltet live på https://grundfast.dk/docs/adressefelt._

Feltet ovenfor er den samme fil, som din side indlæser. På denne side kører den uden nøgle mod vores demo-endpoints; på din side bruger den din offentlige nøgle. Forslagene er de samme.

## Kom i gang

1. **Opret en offentlig nøgle** — Under [Nøgler i dashboardet](https://grundfast.dk/dashboard/keys) vælger du **Offentlig** og skriver de domæner, feltet skal virke på, fx `butik.dk` og `www.butik.dk`. Tilføj `localhost:3000` (eller den port, du udvikler på), hvis du vil prøve lokalt. Brug en testnøgle (`gf_pub_test_`), mens du bygger.

2. **Indsæt scriptet** — Læg tagget før `</body>` eller i `<head>` med `defer`. `data-felt` er en CSS-selektor for det felt, kunden skriver adressen i; `data-postnr` og `data-by` peger på felterne, der skal udfyldes.

```html
<script
  src="https://grundfast.dk/widget/v1/grundfast-adresse.js"
  defer
  data-key="gf_pub_live_..."
  data-felt="#adresse"
  data-postnr="#postnr"
  data-by="#by"
></script>
```

3. **Prøv det** — Skriv en adresse. Vises der ingen forslag, står årsagen i browserens konsol — se [Fejlfinding](#fejlfinding).

Kan du ikke sætte en selektor på scriptet, kan du i stedet markere selve feltet. Her skal indstillingerne have `data-gf-` foran, så de ikke kan forveksles med temaets egne attributter:

```html
<input id="adresse" name="adresse" data-grundfast-adresse
       data-gf-postnr="#postnr" data-gf-by="#by">
```

Felter, der først dukker op senere — en checkout, der tegnes af JavaScript, eller et trin i en formular — får feltet på, når de vises. Guides til de enkelte platforme: [WooCommerce](https://grundfast.dk/woocommerce-adresse-autocomplete), [Shopify](https://grundfast.dk/shopify-adresse-autocomplete) og [alle guides om adresser](https://grundfast.dk/guides).

## Offentlige nøgler

En almindelig nøgle (`gf_live_`, `gf_test_`) er hemmelig og hører til på en server. En offentlig nøgle (`gf_pub_live_`, `gf_pub_test_`) er lavet til at stå i en webside, hvor alle kan læse den. Derfor kan den langt mindre:

- **Kun fra dine domæner.** Kaldet skal komme fra en side på et af nøglens tilladte domæner. API’et læser browserens `Origin`-header (eller `Referer`, hvis `Origin` mangler). Et kald uden nogen af dem afvises.
- **Kun adresseopslag.** Nøglen åbner adressesøgning og autocomplete, opslag af én adresse, datavask af én adresse og postnumre — alle som `GET`. Registre, batch, eksport og din konto kræver en hemmelig nøgle.
- **En grænse pr. besøgende.** Ud over planens burst-grænse får hver IP-adresse højst 120 kald i minuttet med samme nøgle, så én, der kopierer nøglen, ikke kan bruge hele din kvote på et øjeblik.

| Du skriver | Gælder for |
| --- | --- |
| butik.dk | `https://butik.dk` og `http://butik.dk` — ikke `www.butik.dk` |
| www.butik.dk | Kun `www.butik.dk` |
| *.butik.dk | Alle underdomæner, fx `www.butik.dk` og `test.shop.butik.dk` — ikke `butik.dk` selv |
| https://butik.dk | Kun over https |
| localhost:3000 | Din lokale udviklingsserver på port 3000 |

Et domæne uden port gælder kun standardporten. `*` alene og `*.dk` afvises. Et domæne med æ, ø eller å skrives i den form, browseren sender: `xn--`-formen (punycode). Ændrer du listen, slår det igennem inden for et halvt minut.

> **Hvad koster det?** Kald med en offentlig nøgle tæller som alle andre kald, og hvert opslag er ét kald. Feltet spørger først fra to tegn og venter på en kort pause i tastningen, så en kunde, der skriver en adresse, typisk udløser en håndfuld kald — plus ét, når bygningen har lejligheder. En afvist forespørgsel (forkert domæne, forkert endpoint eller over grænsen) tæller ikke.

Origin-tjekket stopper andre websites i at bruge din nøgle fra deres besøgendes browsere. Et script uden for en browser kan skrive en hvilken som helst `Origin`; det er grænsen pr. IP-adresse og de få endpoints, der begrænser skaden. Mistænker du misbrug, så tilbagekald nøglen og opret en ny — det tager et minut at skifte den i scriptet.

Nøglen sendes som `?token=` i et almindeligt `GET`, så browseren ikke skal lave et CORS-preflight først. Vil du kalde endpoints selv med en offentlig nøgle, virker `Authorization: Bearer` også; svaret åbnes for dit domæne med `Access-Control-Allow-Origin`.

```js
const res = await fetch(
  'https://api.grundfast.dk/v1/adresse/search?per_side=8&q=' +
    encodeURIComponent('Jægersborggade 10') +
    '&token=gf_pub_live_...'
);
const { results } = await res.json();
```

## Indstillinger

Alle indstillinger er `data-`-attributter på script-tagget. På et markeret felt skrives de med `data-gf-` foran og gælder kun det felt. Felt-indstillingerne er CSS-selektorer; de slås først op i feltets egen formular.

| Attribut | Betydning |
| --- | --- |
| data-key | Den offentlige nøgle, `gf_pub_live_…` eller `gf_pub_test_…`. |
| data-felt | Selektor for det eller de felter, der skal have forslag, fx `#billing_address_1`. |
| data-postnr, data-by | Felterne til postnummer og by. Er en af dem sat, får adressefeltet kun vej, husnummer, etage og dør; ellers hele adressen. |
| data-adresselinje | Et felt til “Vej 10, 2. th” — hvis det ikke er det felt, kunden skriver i. |
| data-vejnavn, data-husnr | Vejnavn og husnummer hver for sig. |
| data-etage, data-doer | Etage og dør hver for sig (tomme, når der ikke er nogen). |
| data-kommunekode | Firecifret kommunekode, fx `0101`. |
| data-adgangsadresse-id, data-adresse-id | DAR-id’er for husnummeret og for den konkrete bolig. Godt at gemme på ordren. |
| data-bfe | BFE-nummeret for ejendommen, når det er kendt. |
| data-enheder="false" | Spring trinnet med etage og dør over. |
| data-css="false" | Indsæt ikke widgettens eget stylesheet. |
| data-min-length, data-delay | Antal tegn før første opslag (standard 2) og pausen i millisekunder (standard 180). |
| data-observe="false" | Lad være med at holde øje med felter, der dukker op senere. |

## Hændelsen, når en adresse er valgt

Når kunden har valgt, sender feltet hændelsen `grundfast:adresse`. Den bobler op gennem siden, så du kan lytte på `document` eller formularen. `detail` er hele adressen:

```js
document.addEventListener('grundfast:adresse', (e) => {
  const a = e.detail;
  console.log(a.betegnelse, a.bfe_nummer);
});
```

**event.detail**

```json
{
  "betegnelse": "Jægersborggade 10, st. tv, 2200 København N",
  "adresselinje": "Jægersborggade 10, st. tv",
  "vejnavn": "Jægersborggade",
  "husnr": "10",
  "etage": "st",
  "doer": "tv",
  "postnr": "2200",
  "postnrnavn": "København N",
  "kommunekode": "0101",
  "adgangsadresse_id": "0a3f507a-a100-32b8-e044-0003ba298018",
  "adresse_id": "0a3f509f-1325-32b8-e044-0003ba298018",
  "bfe_nummer": 9866621,
  "koordinat": { "lon": 12.5449906, "lat": 55.6921031 }
}
```

1. **Klar til et adressefelt** Vej, husnummer, etage og dør i den form, en pakkelabel skal have.
2. **To id’er** `adgangsadresse_id` er opgangen eller huset; `adresse_id` er den konkrete bolig og er `null`, når der ikke er valgt etage og dør.
3. **Ejendommen** Nøglen til resten af Grundfast: brug den på din server med en hemmelig nøgle, fx til [BBR](https://grundfast.dk/docs/bbr).

## Fra JavaScript

I en app, der selv tegner sine felter, kobler du feltet på med `GrundfastAdresse.attach` og fjerner det igen med `destroy()`. Indstillingerne er de samme som attributterne, skrevet i camelCase, og felterne kan være elementer i stedet for selektorer.

```js
const felt = GrundfastAdresse.attach(document.querySelector('#adresse'), {
  key: 'gf_pub_live_...',
  postnr: '#postnr',
  by: '#by',
  onSelect: (adresse) => gemPaaOrdren(adresse),
});

// Når formularen forsvinder:
felt.destroy();
```

Værdierne skrives, som hvis kunden selv havde tastet dem: feltets egen setter og bagefter `input`- og `change`-hændelser. Derfor opdaterer React-, Vue- og blok-checkouts deres tilstand uden ekstra kode.

## Udseende

Forslagslisten arver skrifttypen fra siden. Farver og hjørner styres med CSS-variabler, som du sætter på `:root`:

```css
:root {
  --gf-adr-bg: #fff;          /* listens baggrund */
  --gf-adr-fg: #1a1a1a;       /* tekst */
  --gf-adr-border: #c8c8c8;
  --gf-adr-active: #eef3ff;   /* forslaget, der er markeret */
  --gf-adr-active-fg: inherit;
  --gf-adr-radius: 6px;
}
```

Vil du style alt selv, så sæt `data-css="false"` og brug klasserne `.gf-adr-liste` (listen), `.gf-adr-valg` (et forslag; det markerede har `aria-selected="true"`) og `.gf-adr-note` (overskriften i trinnet med etage og dør).

Feltet er en tilgængelig combobox: piletasterne flytter i listen, Enter vælger, Escape lukker, og en skærmlæser får antallet af forslag læst op. Enter sender stadig formularen, når intet forslag er markeret.

## Versioner og cache

| URL | Indhold | Cache |
| --- | --- | --- |
| /widget/v1/grundfast-adresse.js | Seneste 1.x. Får rettelser og nye indstillinger, men aldrig ændringer, der bryder en eksisterende opsætning. | 1 time |
| /widget/1.0.0/grundfast-adresse.js | Præcis version 1.0.0. Filen ændres aldrig. | 1 år, immutable |

Vil du låse filen med Subresource Integrity, så brug den faste version:

```html
<script
  src="https://grundfast.dk/widget/1.0.0/grundfast-adresse.js"
  integrity="sha384-zgjWtsuGrsYs+BZIp0mG5VEIvkOffWtOKqoxEBZG2u3Zbu4cnV9DVPhiFd+o/+ia"
  crossorigin="anonymous"
  defer
  data-key="gf_pub_live_..."
  data-felt="#adresse"
></script>
```

## Fejlfinding

Feltet blokerer aldrig formularen: fejler et opslag, lukker listen, og kunden kan skrive adressen selv. Årsagen skrives én gang i browserens konsol med `[grundfast-adresse]` foran.

| I konsollen | Årsag | Gør dette |
| --- | --- | --- |
| `origin https://… is not allowed for this public key` | Siden ligger på et domæne, der ikke står på nøglen. | Tilføj domænet under Nøgler. Husk både `butik.dk` og `www.butik.dk`, eller brug `*.butik.dk`. |
| `public key requires an Origin or Referer header` | Siden sender hverken `Origin` eller `Referer`, fx på grund af en streng `Referrer-Policy` i en iframe. | Tillad mindst `origin` i sidens `Referrer-Policy`. |
| `public keys only open the address autocomplete endpoints` | Nøglen bruges til et andet endpoint end adresseopslag. | Kald det endpoint fra din server med en hemmelig nøgle. |
| `no public key` | Scriptet mangler `data-key`. | Sæt `data-key="gf_pub_live_…"` på script-tagget. |
| `rate limit exceeded` | Mere end 120 kald i minuttet fra samme IP med samme nøgle, eller planens burst-grænse. | Vent; listen virker igen efter `Retry-After` sekunder. |

Har din side en Content-Security-Policy, skal `script-src` tillade `https://grundfast.dk` og `connect-src` tillade `https://api.grundfast.dk`. Widgetten indsætter et lille stylesheet; tillader din `style-src` ikke det, så sæt `data-css="false"` og style klasserne selv. Fejlkoderne står samlet under [Fejl og statuskoder](https://grundfast.dk/docs/fejl).
