Naslag

Veldtypen

De ondersteunde veldtypen en hun instellingen.

Veldtypen

Type Beschrijving
text Tekst op één regel
textarea Platte tekst op meerdere regels
html Opgemaakte tekst (bubble-editor en veldbalk, zie Editorknoppen)
slug URL-veilig slugveld, berekend uit de ingestelde bronvelden
boolean Schakelaar waar/onwaar
date Datumkiezer
email E-mailadres
url URL
password Versleuteld wachtwoordveld
image Upload van één afbeelding
file Upload van één bestand
upload Algemene upload
gallery Upload van meerdere afbeeldingen
select Keuzelijst, met vaste opties of uit een resource
number Getalinvoer, met optioneel min en max
color Kleurkiezer
location-picker Interactieve wereldkaart die de breedtegraad in het veld bewaart en de lengtegraad in het bijbehorende veld uit longitude_field

Namen van veldtypen moeten precies kloppen.

Veldinstellingen

Gangbare opties per veld:

Sleutel Type Beschrijving
name string Label in de admin
required boolean Validatie; zet dit op alle titel- en naamvelden
admin_col boolean Tonen in de overzichtstabel van de admin (standaard: true)
admin_filter boolean of object Voegt een filter toe aan het overzicht van de admin voor een veld select of boolean. Gebruik { "default": <stored-value> } om het aan te zetten met een standaardkeuze, en optioneel "feature": "permission-name" om het alleen te tonen aan gebruikers met dat recht. Los van admin_col.
multi boolean Meerdere waarden toestaan
i18n boolean Per taal vertalen
accept string Beperking van bestandstypen voor image- en file-velden

HTML-velden gebruiken uitdrukkelijk een van twee instellingen:

Eenvoudig (korte tekstblokken, zoals een intro):

"buttons": "bold,italic",
"admin_col": false

Uitgebreid (volledige content, zoals een body):

"buttons": "h2,h3,h4,bold,italic,orderedlist,unorderedlist,quote,anchor",
"media": true,
"media_sizes": "sm-90,md-70,lg-60,xl-50,xxl-40",
"admin_col": false

Definieer nooit een html-veld zonder een van deze te kiezen. media_sizes is verplicht bij media: true.

Editorknoppen. buttons noemt de opmaakknoppen van een html-veld, in volgorde. Zonder buttons krijgt een veld bold,italic,removeFormat. Ingebouwd:

Knop Doet
bold, italic Opmaak in de tekst (Ctrl/Cmd+B, Ctrl/Cmd+I)
h2, h3, h4, quote, pre Blokopmaak; nog een keer maakt er weer een alinea van
orderedlist, unorderedlist Lijsten
anchor Link: toevoegen, bewerken (vooraf ingevuld) of verwijderen; Ctrl/Cmd+K
removeFormat Opmaak wissen
strikethrough, subscript, superscript, underline Optioneel, in geen enkele standaardset

Dezelfde knoppen staan op twee plekken: de bubble-werkbalk bij geselecteerde tekst, en de veldbalk. In admin-formulieren hoort de veldbalk bij het veld; bij inline bewerken zweeft hij boven het actieve veld en bevat hij ook Media (bij media: true) en Save (Ctrl/Cmd+S). Alt+F10 zet de toetsenbordfocus op de veldbalk. Plakken houdt alleen platte tekst over, tenzij het veld "paste_html": true zet; dat houdt basisopmaak vast en haalt de markup van Word en Google Docs weg.

Eigen editorknoppen. Een project definieert eigen knoppen als JSON in ext/tpl/bubble-editor-buttons/index.tpl (de template mag shortcodes gebruiken, zoals [#text ...#] voor labels), en noemt ze dan net als de ingebouwde in de buttons van een veld:

{
    "highlight": { "kind": "wrap", "tag": "mark", "label": "[#text Highlight#]", "icon": "M" },
    "h5": { "kind": "block", "tag": "h5", "label": "Heading 5", "icon": "H5" },
    "poi": {
        "kind": "event", "event": "nb:insert-point", "insert": true, "bubble": true,
        "active": "[data-poi-link]", "label": "[#text Point of interest#]", "icon": "<svg ...></svg>"
    }
}
Soort Doet
command Een opmaakcommando ("command": "bold")
block Blokopmaak ("tag": "h5")
list Lijstcommando
wrap Zet de selectie in een element ("tag", optioneel "class"); nog een keer haalt het weg
insert Voegt vaste HTML in bij de cursor ("html")
event Stuurt een DOM-event op het veld ("event", bubbling, detail: { editor, range }) voor eigen code van het project, bijvoorbeeld om een kiezer te openen
link Vraagt om een URL, zoals anchor

Opties: "insert": true zet een knop naast Media in de veldbalk in plaats van bij de opmaakknoppen; voeg "bubble": true toe als hij ook op geselecteerde tekst werkt; "active" is een selector die de knop actief toont als de cursor in een match staat. Links zijn a[href]: een project mag <a> zonder href gebruiken voor eigen elementen in de tekst. Code kan ook knoppen registreren met nb.bubble_editor.register(name, { label, icon, run(ctx), is_active(ctx) }).

De bewerktools van een project (de modal en het script achter een event-knop) staan in ext/tpl/edit-extras/index.tpl, die wordt getoond voor gebruikers die inline mogen bewerken, op pagina's van de site en van de admin.

Slugvelden:

Slugs zijn aparte velden. Zet geen "slug": true op een ander veld. Definieer een apart veld met "type": "slug" en een source die wijst naar het veld of de velden waar hij van wordt afgeleid.

Het admin-formulier berekent de slug in de browser uit de bronvelden, voordat het formulier wordt verstuurd. De API verzint bij het opslaan geen ontbrekende slugvelden; hij bewaart de waarde van het slugveld die hij krijgt. Maakt of importeert code records buiten het admin-formulier om, dan moet die code ook de waarde van het slugveld zetten.

Zet "preserve_existing": true op het slugveld om ontbrekende slugs te maken terwijl opgeslagen URL's gelijk blijven als het formulier opent of de brontitel verandert. Redacteuren kunnen een opgeslagen slug nog steeds bewust aanpassen.

"url_slug": {
  "name": "URL slug",
  "type": "slug",
  "source": "title",
  "admin_col": false
}

Meerdere bronnen scheid je met komma's; de waarden worden met een spatie samengevoegd voordat er een slug van wordt gemaakt:

"url_slug": {
  "name": "URL slug",
  "type": "slug",
  "source": "title,date",
  "admin_col": false
}

Met "source": "title,date", titel "Jazz Night" en datum "2026-04-09" wordt de slug jazz-night-2026-04-09. Zet url_slug altijd in de lijst index van de resource, zodat hij in route.inc kan worden opgezocht.

Is het bronveld i18n, dan wordt het slugveld ook als i18n behandeld. Zo geven title.nl, title.en en title.fr overeenkomende waarden onder title_slug.nl, title_slug.en en title_slug.fr. Vertaal slugs niet met de hand; laat het slugveld ze afleiden uit de bronwaarden per taal.

Selectievelden:

Vaste opties:

"type": "select",
"options": {
  "new": "New",
  "active": "Active",
  "closed": "Closed"
}

Opties uit een andere resource:

"type": "select",
"resource": "categories"

Filters in het overzicht van de admin zet je per veld aan voor select- en boolean-velden:

"status": {
  "type": "select",
  "options": { "active": "Active", "closed": "Closed" },
  "admin_filter": { "default": "active" }
},
"published": {
  "type": "boolean",
  "admin_col": false,
  "admin_filter": true
}

Filters vergelijken de ruwe opgeslagen waarden, terwijl ze de ingestelde opties of labels van de gekoppelde resource tonen. Selecties met meerdere waarden passen als het record de gekozen waarde bevat. Meerdere filters worden met EN gecombineerd en blijven werken met zoeken, sorteren en pagineren. Keuzes blijven in de URL staan als filter[field]=value. Een ontbrekende parameter gebruikt de ingestelde standaard; een lege parameter kiest uitdrukkelijk All. Ongeldige waarden in de URL worden All.

Locatievelden:

Gebruik location-picker als redacteuren coördinaten op een kaart moeten kiezen in plaats van ze te typen. Het kiezerveld bewaart de breedtegraad in zijn eigen veld en werkt een apart numeriek veld voor de lengtegraad bij, genoemd in longitude_field. Verberg het bijbehorende veld met wrapper_class: "hidden", zodat de redacteur één bedieningselement ziet terwijl de resource eenvoudige losse coördinaten bewaart:

"location_lat": {
  "name": "Location",
  "type": "location-picker",
  "longitude_field": "location_lng",
  "required": true,
  "admin_col": false
},
"location_lng": {
  "name": "Longitude",
  "type": "number",
  "required": true,
  "min": -180,
  "max": 180,
  "wrapper_class": "hidden",
  "admin_col": false
}

Hoe formuliervelden worden opgebouwd

Deze sectie beschrijft de hele weg van een velddefinitie in een resource tot een getoond formulierveld. Lees dit voordat je een nieuw veldtype bouwt.

Overzicht

.meta field definition
  ↓
render_field() - PHP (core/modules/forms/lib/render-field.php)
  ↓
_f.* template variables
  ↓
[#field-{type}#] template (core/modules/forms/tpl/field-{type}/index.tpl)
  ↓
Alpine.js form_data.{field}   ← x-model binding
  ↓
API POST/PUT on submit

render_field() en de context _f.*

render_field() is de ingang. Het:

  1. Neemt een array met de velddefinitie, de sleutel van het veld, en optionele parameters voor waarde, opslag en bron
  2. Verspreidt de hele velddefinitie over _f.* via set_variable_dot('_f', $def); elk attribuut uit de .meta is automatisch beschikbaar in de template
  3. Zet daar berekende variabelen bovenop: _f.key, _f.title, _f.value, _f.model, _f.required, _f.ai, _f.bg
  4. Geeft door aan [#field-{type}#], de template voor dat veldtype

_f.model is de Alpine.js-expressie voor x-model, berekend als {store}.{field}. De standaardopslag is form_data. Bij i18n-velden wordt het form_data.{field}[{lang}].

Standaardvariabelen _f.* die in elke veldtemplate beschikbaar zijn:

Variabele Inhoud
_f.key Veldnaam / HTML-attribuut name
_f.title Label (uit name in .meta)
_f.value Vooraf ingevulde waarde
_f.model Alpine-expressie voor x-model, bijvoorbeeld form_data.email
_f.required Of het veld verplicht is
_f.bg Class voor de achtergrondkleur van het zwevende label
_f.* (elke) Alle andere sleutels van de velddefinitie: _f.accept, _f.options, _f.resource, enzovoort

form_data: de Alpine.js-store

form_data is het reactieve Alpine.js-object met alle huidige veldwaarden. Het wordt op het element <form> gedeclareerd. Elk veld schrijft zijn waarde hierin via x-model="[#_f.model#]".

Bij het versturen wordt form_data in de API-payload gezet en met POST naar /api/v1/{resource} gestuurd. Velden met een rijke editor (HTML, gallery, image) schrijven hun waarden via een apart mechanisme (nb.edit.get_field_values()), dat automatisch wordt samengevoegd; veldtemplates hoeven dat niet zelf te regelen.

De eenvoudigste veldtemplate

field-default/index.tpl is de basis voor tekstachtige velden:

<div class="[#_f.wrapper_class#]">
    <input type="[#_f.type#]" value="[#_f.value#]" name="[#_f.key#]"
        x-init="[#_f.model#]=`[#_f.value#]`"
        x-model="[#_f.model#]"
        [#if _f.required=(not-empty) echo=required#]
        class="input input-bordered w-full" />
    <label class="pointer-events-none absolute left-3 -top-2.5 px-1
            font-bold text-sm leading-tight [#get _f.bg default=bg-neutral-50#]">
        [#_f.title#][#if _f.required=(not-empty) echo=" *"#]
    </label>
</div>

Een nieuw veldtype toevoegen

  1. Maak de template:

    core/modules/forms/tpl/field-{type}/index.tpl   ← core types
    ext/tpl/field-{type}/index.tpl                   ← project-specific types
    
  2. In de template:

    • Koppel aan Alpine via x-model="[#_f.model#]"
    • Zet de beginwaarde met x-init="[#_f.model#]='[#_f.value#]'"
    • Gebruik [#_f.key#] als HTML-attribuut name
    • Lees opties van het type via _f.* (bijvoorbeeld [#_f.accept#] voor beperkingen van bestandstypen)
  3. Gebruik de typenaam in .meta; hij werkt zodra de template bestaat.

  4. Beheert het veld ingewikkelde status (zoals een bestandskiezer of een knop voor de mediabrowser), nest dan een eigen x-data binnen de x-data van het formulier. De buitenste form_data blijft bereikbaar vanuit geneste scopes.

Eigen velden gebruiken de afspraak field-{type} voor templates en de context _f.* (_f.model, _f.key, _f.title, _f.bg, enzovoort). Veldtemplates uit een oudere generatie, met _fmodel, _fid, _ftitle, _fbg, _bf_render_field() of Tailwind Elements-attributen data-te-*, worden in deze vorm opnieuw gebouwd in plaats van overgenomen.

Geneste velden en herhaalvelden moeten de geneste modelpaden van Alpine zelf regelen. Een veld group dat locations bewaart, koppelt de onderliggende velden bijvoorbeeld aan paden zoals form_data.locations[ix].location, of geeft een expliciet model mee bij hergebruik van render_field(). Ga er niet van uit dat de renderer van core geneste arraypaden uit een platte veldsleutel kan afleiden.

Bekijk deze pagina als Markdown