# Formulieren

Formulieren die records schrijven, met bescherming tegen vervalsing, spamfilter en validatie.

Een formulier op de site schrijft een record naar een resource. Nimbly maakt het formulier uit een kleine JSON-definitie, beschermt het tegen vervalste en geautomatiseerde inzendingen, valideert het tegen de resource en slaat het resultaat op zoals elk ander record.

## Een contactformulier

1. **De resource** die de inzendingen ontvangt, `ext/data/leads/.meta`:

```json
{
  "name": {"singular": "Lead", "plural": "Leads"},
  "fields": {
    "name": {"name": "Name", "type": "text", "required": true},
    "email": {"name": "Email", "type": "email", "required": true},
    "message": {"name": "Message", "type": "textarea", "admin_col": false}
  },
  "sort": {"field": "_created", "flags": "numeric", "order": "desc"}
}
```

2. **De formulierdefinitie**, naast de route die het formulier toont, `ext/uri/contact/contact.json`:

```json
{
  "name": "contact",
  "resource": "leads",
  "success_message": "Thank you, we will be in touch.",
  "fields": {
    "name": {"type": "text", "name": "Your name", "required": true},
    "email": {"type": "email", "name": "Email address", "required": true},
    "message": {"type": "textarea", "name": "Message"}
  },
  "buttons": [{"type": "submit", "title": "Send"}]
}
```

3. **Het formulier op de pagina**, in `ext/uri/contact/main.tpl`:

```html
[#build-form contact#]
```

4. **Open de ene handeling die bezoekers nodig hebben.** De API is dicht voor anonieme bezoekers. Sta alleen het aanmaken van leads toe, met een route op `ext/uri/api/v1/leads/index.tpl`:

```html
[#api-allow post leads#]
```

Al het andere op de resource leads blijft beschermd. Inzendingen verschijnen in de admin onder Leads.

## Wat het formulier voor je doet

- **Bescherming tegen vervalsing.** Elk formulier heeft een sleutel die aan de sessie van de bezoeker is gekoppeld.
- **Spamfilter.** Een verborgen honeypotveld houdt eenvoudige bots tegen.
- **Validatie.** Verplichte velden en veldtypen worden in de browser gecontroleerd en nog eens op de server, tegen de definitie van de resource.
- **Versturen zonder de pagina te herladen**, met de succesmelding op dezelfde plek.

## Velden

Formuliervelden gebruiken de veldtypen van resources: `text`, `textarea`, `email`, `url`, `select`, `upload` en andere. `"help"` zet een hint onder een veld. Velden kun je naast elkaar groeperen:

```json
"fields": {
  "_g1": {"type": "group_start", "name": "Name"},
  "first_name": {"type": "text", "name": "First name"},
  "last_name": {"type": "text", "name": "Last name"},
  "_g1e": {"type": "group_end"}
}
```

Gebruik voor een bestandsupload een `upload`-veld met `accept`, en noem het in `"upload_field"` op het hoogste niveau van de definitie.

## Opmaak

Het formulier krijgt de classes `nb-form` en `nb-form-<name>`, en elke veldomhulling `nb-field` en `nb-form-field-<name>`. Geef ze opmaak in `ext/theme.css`, of vervang de classes van de omhulling per formulier met `[#build-form contact field-wrapper-class="my-2"#]`.

## Na een inzending

Om een bevestiging te sturen of het team te laten weten dat er iets binnenkwam, declareer je een event op de resource. Een `job:`-regel zet het werk in de wachtrij, zodat de bezoeker nooit op de mailserver hoeft te wachten:

```json
"events": {"create": ["job:lead-created"]}
```

Zie [Werk op de achtergrond](/nl/docs/guides/jobs/) voor de handler.

## Je eigen formuliermarkup

`[#build-form#]` is een gemak, geen verplichting. Een eigen formulier, in elke markup en met elke frontendcode, kan naar hetzelfde API-endpoint posten. In de standaardstack stuurt `nb.api.post()` een record vanuit Alpine. Voor verwerking die verder gaat dan een record opslaan, kan een route de handlers `validate_<name>.inc` en `post_<name>.inc` bevatten, die bij het versturen draaien.
