Lookups — Auswahllisten und Vorbelegung¶
Lookups sind das Mittel, mit dem ein Feld seine Werte aus einer Quelle zieht: aus einer festen Liste, einer Datenbank-Tabelle, freiem SQL, einem Skript oder einem LDAP-Verzeichnis. Dazu gehören:
| Schlüssel | Wofür? |
|---|---|
kc:lookupOptions |
feste Auswahlliste direkt im Schema |
kc:lookup |
Auswahlliste aus Datenbank, externer Datenquelle, SQL, Skript oder LDAP |
kc:lookupDisplay |
wie der gewählte Wert in Druck und Listen erscheint |
kc:valueLookup |
einen Wert aus der Datenbank nachziehen |
kc:defaultLookup |
Vorbelegung neuer Datensätze per SQL |
kc:defaultExpression |
Vorbelegung neuer Datensätze per JavaScript |
Wer Datensätze mit mehreren Feldern aus einer Suche übernehmen will, nimmt einen Picker. Und wer in EINEM Feld mehrere Werte unterbringen will — etwa eine Länderauswahl —, findet die Eingabearten dafür unter Mehrere Werte in einem Feld.
Zusammenklicken reicht — das JSON ist zum Nachschauen da
Auswahllisten richtest du im Formular-Editor ein. Eine feste Werteliste, eine Liste aus einer Datenbank-Tabelle und eine Liste aus einem LDAP-Verzeichnis klickst du vollständig zusammen, ohne eine Zeile zu tippen. Nur die beiden freien Quellen — eigenes SQL und Skript — tippst du selbst; auch dafür hat der Editor ein eigenes Eingabefeld samt Vollbild-Ansicht.
Die eingeklappten Kästen „Hinter den Kulissen" auf dieser Seite zeigen, was der Editor dabei in das Formular schreibt. Aufklappen lohnt sich, wenn du ein Formular exportieren und mitnehmen willst oder wissen möchtest, warum eine Liste leer bleibt — zum Arbeiten brauchst du es nicht.
Im Formular-Editor¶
Im Eigenschaften-Dialog eines Feldes, Reiter Auswahl, wählst du den Auswahl-Typ:
| Auswahl-Typ | Ergebnis im Schema |
|---|---|
| Freie Eingabe | kein Vorschlag |
| Feste Werteliste | kc:lookupOptions als einfache Liste (z. B. 2026, 2027) |
| Liste mit Code + Text | kc:lookupOptions mit key/value — gespeichert wird der Code |
| Aus Datenbank / Verzeichnis | kc:lookup — Tabelle, SQL, Skript oder LDAP |
| Such-Dialog | kc:picker — siehe Picker |
Darunter stehen die Schalter, die für alle Listen gelten:
| Schalter | Schlüssel | Wirkung |
|---|---|---|
| Lookup aktiv (aus / ein / bedingt) | kc:lookupActiveIf |
Liste nur unter einer Bedingung anbieten |
| Nur Werte aus Liste | kc:lookupStrict |
Anwender muss einen Listeneintrag wählen |
| Beim Betreten öffnen | kc:lookupOpenOnFocus |
Liste klappt beim Hineinklicken auf |
| Gewählten Wert nicht erneut anbieten | kc:hideSelectedOption |
gewählten Eintrag in der Liste ausblenden |
| Anzeige | kc:lookupDisplay |
siehe unten |
Werteliste als Vorschlag
Eine Feste Werteliste ohne Nur Werte aus Liste wird als Textfeld mit Vorschlägen dargestellt — die Liste hilft, erlaubt aber eigene Eingaben. Mit dem Schalter wird daraus ein echtes Auswahlfeld, und auch beim Speichern prüft KCForms, dass der Wert aus der Liste stammt. Bei nachgeladenen Listen (Datenbank, LDAP) und bei „Lookup aktiv: bedingt“ prüft nur die Oberfläche.
Auswahl aus einer festen Werteliste¶
Die häufigste Liste tippst du direkt im Editor ein. Es gibt sie in zwei Bauarten: als reine Textliste — oder als Liste mit Code + Text, bei der der Anwender den Klartext sieht („Frau"), gespeichert aber ein kurzer Code wird („FRAU"). Die zweite Bauart lohnt sich immer dann, wenn der Text später einmal anders lauten könnte, die gespeicherten Daten aber stabil bleiben sollen.
Hinter den Kulissen: feste Werteliste mit Code und Klartext
"anrede": {
"type": "string", "title": "Anrede",
"kc:lookupOptions": [
{ "key": "FRAU", "value": "Frau" },
{ "key": "HERR", "value": "Herr" },
{ "key": "DIVERS", "value": "Divers" }
]
}
key wird gespeichert, value angezeigt.
Die reine Textliste ist noch kürzer — sie besteht nur aus den Texten selbst:
"kc:lookupOptions": ["2026", "2027", "2028"].
Mit "kc:widget": "radio" erscheint die Liste als Radio-Gruppe
(kc:radioColumns für mehrere Spalten, kc:allowDeselect: true erlaubt
das Abwählen).
Auswahl aus Datenbank, Skript oder Verzeichnis¶
kc:lookup kennt fünf Quellen. Sie schließen sich gegenseitig aus.
1. Aus einer Datenbank-Tabelle¶
Der Normalfall: Du wählst im Editor die Tabelle, die Spalte mit dem zu speichernden Schlüssel und die Spalte mit dem Anzeigetext. Dazu kommen nach Bedarf eine Sortierung, ein fester Filter, eine Obergrenze für die Zahl der Einträge und — falls die Daten gar nicht in der KCForms-Datenbank liegen — eine externe Datenquelle.
Hinter den Kulissen: Auswahlliste aus einer Datenbank-Tabelle
"land": {
"type": "string", "title": "Land",
"kc:lookup": {
"table": "country",
"keyColumn": "code",
"valueColumn": "label",
"orderBy": "label ASC",
"limit": 500
}
}
| Schlüssel | Standard | Bedeutung |
|---|---|---|
table |
(Pflicht) | Tabellenname |
keyColumn |
— | Spalte, deren Wert gespeichert wird |
valueColumn |
= keyColumn |
Spalte für den Anzeigetext |
displayColumns |
— | mehrere Spalten als Anzeigetext, siehe unten |
separator |
, |
Trennzeichen zwischen den displayColumns |
column |
— | Kurzform, wenn Schlüssel und Anzeige gleich sind |
where |
— | Bedingung, auch mit ${feld}-Platzhaltern |
orderBy |
— | Sortierung, z. B. label oder label DESC |
distinct |
false |
doppelte Werte zusammenfassen |
limit |
1000 |
höchstens so viele Einträge (1–10 000) |
dataSourceId |
— | statt der KCForms-Datenbank eine externe Datenquelle abfragen |
Tabellen- und Spaltennamen müssen gültige SQL-Bezeichner sein.
Anzeigetext aus mehreren Spalten¶
Soll im Aufklappfeld „Nachname, Vorname" stehen, wähle im Editor mehrere Anzeige-Spalten aus. Die Reihenfolge deiner Auswahl ist die Reihenfolge im Text; das Trennzeichen legst du daneben fest.
"besteller_id": {
"type": "string", "title": "Besteller",
"kc:lookup": {
"table": "USERS",
"keyColumn": "ID_USER",
"displayColumns": ["LAST_NAME", "FIRST_NAME"],
"separator": ", ",
"orderBy": "LAST_NAME, FIRST_NAME"
}
}
Warum nicht einfach selbst CONCAT schreiben?
Weil es keine Schreibweise gibt, die auf allen vier unterstützten Datenbanken funktioniert — gemessen am 28.09.2026:
| Schreibweise | MySQL | PostgreSQL | Oracle | SQL Server |
|---|---|---|---|---|
CONCAT(a, b, c) |
✔ | ✔ | ✘ | ✔ |
CONCAT(CONCAT(a, b), c) |
✔ | ✔ | ✔ | ✔ |
a || b || c |
liefert 0 |
✔ | ✔ | ✘ |
a + b + c |
liefert 0.0 |
✘ | ✘ | ✔ |
Oracles CONCAT nimmt genau zwei Argumente; beliebig viele verkettet man
dort mit ||. Nur ist || auf SQL Server ein Syntaxfehler und auf MySQL
das logische ODER — dort kommt ohne jede Fehlermeldung eine Spalte
voller Nullen heraus. Mit displayColumns baut KCForms den Ausdruck
selbst, passend zur laufenden Datenbank, und hüllt jede Spalte in
COALESCE(…, '') — sonst fiele auf MySQL die ganze Zeile aus der Liste,
sobald eine der Spalten leer ist.
Brauchst du mehr als eine Verkettung (Join, CASE), nimm freies SQL — dann
gilt allerdings die Tabelle oben, und die Abfrage ist an die Datenbank
gebunden, für die du sie geschrieben hast.
2. Mit einer eigenen SQL-Abfrage¶
Für Joins, CASE oder COALESCE reicht die Tabellenauswahl nicht — dann
schreibst du die Abfrage selbst. Die erste Spalte des Ergebnisses ist der
gespeicherte Wert, die zweite (falls vorhanden) der Anzeigetext. Das
Beispiel unten baut so eine Ansprechpartner-Liste aus zwei Tabellen
zusammen und zeigt Vor- und Nachnamen in einer Zeile.
Hinter den Kulissen: Auswahlliste aus freiem SQL
"kontaktperson": {
"type": "string", "title": "Ansprechpartner",
"kc:lookup": {
"sql": "SELECT k.id_user, CONCAT(u.FIRST_NAME, ' ', u.LAST_NAME) FROM kontakt k JOIN USERS u ON k.id_user = u.ID_USER WHERE k.kunde_id = ${kundeId} ORDER BY u.LAST_NAME"
}
}
Auch hier wirkt dataSourceId.
Freies SQL bindet dich an eine Datenbank
Das Beispiel oben benutzt CONCAT mit drei Argumenten — das läuft auf
MySQL, PostgreSQL und SQL Server, aber nicht auf Oracle. Wenn dein
Formular auf mehreren Datenbanken laufen soll, nimm für reine
Spalten-Verkettungen displayColumns (siehe oben) statt freiem SQL.
Zwei Sicherungen: Die Abfrage muss mit SELECT oder WITH beginnen, und
Befehle wie INSERT, UPDATE, DELETE, DROP oder ALTER werden
abgelehnt.
3. Aus einem Skript¶
Wenn sich die Einträge weder als Tabelle noch als SQL-Abfrage beschreiben
lassen, baust du die Liste mit voller JavaScript-Logik selbst zusammen und
meldest jeden Eintrag einzeln an. Die Quelle heißt dann script.
Hinter den Kulissen: Auswahlliste aus einem Skript
Im Skript stehen handle({ key, value }) (fügt einen Eintrag hinzu),
db, log, kc und declaration.elements zur Verfügung. Skript-Lookups
dürfen mindestens 30 Sekunden laufen — länger als normale Hooks.
4. Aus einem LDAP-Verzeichnis¶
Werte aus einem Verzeichnis, z. B. für eine Benutzerauswahl: Du wählst den Konnektor und legst fest, ab welchem Ast gesucht wird, welcher Filter gilt und welches Attribut gespeichert bzw. angezeigt wird.
Hinter den Kulissen: Auswahlliste aus einem LDAP-Verzeichnis
"sachbearbeiter": {
"type": "string", "title": "Sachbearbeiter",
"kc:lookup": {
"ldapConnector": "kcads",
"ldapBase": "ou=Users,dc=kcforms,dc=test",
"ldapFilter": "(objectClass=inetOrgPerson)",
"keyAttr": "uid",
"valueAttr": "cn"
}
}
| Schlüssel | Bedeutung |
|---|---|
ldapConnector |
Name des LDAP-Konnektors (Pflicht) |
ldapBase |
Suchbasis; ohne Angabe die Basis des Konnektors |
ldapFilter |
LDAP-Filter; Standard (objectClass=*) |
keyAttr |
gespeichertes Attribut (Pflicht), auch dn |
valueAttr |
angezeigtes Attribut; Standard = keyAttr |
5. Feste Liste als Quelle¶
kc:lookup akzeptiert auch eine Liste unter options (gleiches Format wie
kc:lookupOptions). Für neue Formulare ist kc:lookupOptions der übliche
Weg — so schreibt es auch der Formular-Editor.
Platzhalter¶
where und sql verstehen Platzhalter, die sicher als Parameter an die
Datenbank gehen (keine SQL-Injection):
| Platzhalter | Quelle |
|---|---|
${feldname} |
aktueller Wert des Feldes — für abhängige Listen |
${user.name} |
Login-Name des angemeldeten Benutzers |
${user.idUser} |
Benutzer-ID |
${user.firstName}, ${user.lastName}, ${user.email} |
Name und E-Mail |
${user.locale}, ${user.roles} |
Sprache, Rollen |
Die ${user.…}-Werte stammen immer vom Server, beim Öffnen des Formulars
ebenso wie beim Nachladen einer Liste. Ein Filter wie
LOGIN_NAME = ${user.name} lässt sich also nicht vom Browser aus auf einen
anderen Benutzer umbiegen. Deshalb ist ein Formularfeld namens user in
Lookup-Platzhaltern nicht erreichbar.
Damit entstehen abhängige Listen: Das Bundesland-Feld bietet nur die
Bundesländer des Landes an, das im Feld land steht — im Filter steht dafür
der Platzhalter ${land} statt eines festen Werts.
Hinter den Kulissen: Bundesland-Liste, gefiltert über das Feld land
Ist land leer, wird der Platzhalter zu NULL — country_code = NULL
trifft nichts, die Liste bleibt leer. Genau so gewollt.
Abhängige Listen¶
Enthält ein Lookup einen ${feld}-Platzhalter, merkt sich KCForms die
Abhängigkeit (kc:lookupDependsOn, wird automatisch gesetzt). Ändert sich
feld, lädt das Formular die abhängige Liste neu. Passt der bisher
gewählte Wert nicht mehr zur neuen Liste, wird er geleert.
${user.…}-Platzhalter lösen kein Neuladen aus.
Wie der gewählte Wert später erscheint¶
Der gespeicherte Schlüssel ist oft ein Code ("DE"), angezeigt werden soll
der Klartext. kc:lookupDisplay steuert die Darstellung in Druck und
Listen:
| Wert | Darstellung |
|---|---|
| (leer) — Auto | Klartext, wenn Schlüssel und Klartext gleich sind; sonst beides |
value |
nur der Klartext — „Deutschland" |
key-value |
beides — „DE — Deutschland" |
key |
nur der Schlüssel — „DE" |
Einen einzelnen Wert nachziehen¶
Manchmal braucht es keine Liste, sondern einen Wert aus einer anderen
Tabelle. Beispiel: Der Anwender tippt eine Lieferantennummer, ein Feld
daneben zeigt den Namen. Dafür gibt es kc:valueLookup. Eingestellt wird
dasselbe wie bei einer Liste — Tabelle, Schlüssel- und Anzeigespalte —
plus ein Filter, der die passende Zeile findet.
Hinter den Kulissen: Lieferantenname zur eingetippten Nummer
Ändert sich lieferantennummer, holt das Formular den Wert nach und
schreibt ihn in lieferantenName; ohne Treffer bleibt das Feld leer.
Es gelten dieselben Quellen wie bei kc:lookup — genommen wird der
Anzeigewert des ersten Treffers.
Vorbelegung neuer Datensätze¶
Im Formular-Editor: Eigenschaften-Dialog, Reiter Erweitert → Vorbelegung
mit den Varianten Wert (kc:defaultValue), Skript (JS)
(kc:defaultExpression) und SQL-Select (kc:defaultLookup).
Die Vorbelegung greift nur bei neuen Datensätzen und nur, wenn das
Feld noch leer ist. onLoad läuft vorher — was ein Skript setzt, gewinnt.
Reihenfolge: erst kc:defaultLookup, dann kc:defaultExpression als
Rückfallstufe.
Vorbelegung per SQL-Select¶
Ein SELECT, das genau einen Wert liefert — typisch der ausgeschriebene
Name des angemeldeten Benutzers, der beim Anlegen im Erfasser-Feld stehen
soll.
Hinter den Kulissen: Erfassername aus der Benutzertabelle vorbelegen
Gleiche Möglichkeiten wie kc:lookup (Tabelle, SQL, Skript, Datenquelle).
Vorbelegung per JavaScript-Ausdruck¶
Ein einzelner JavaScript-Ausdruck, dessen Ergebnis in das Feld wandert — etwa das aktuelle Datum mit Uhrzeit oder eine Begrüßung, die den Vornamen des angemeldeten Anwenders einsetzt (und ohne Vornamen auf ein schlichtes „Hallo" zurückfällt).
Hinter den Kulissen: Datum und Begrüßung vorbelegen
Der Ausdruck hat dieselben Möglichkeiten wie ein onLoad-Skript (ctx,
db, log, kc, fs, xml, mail, eingebundene
Bibliotheken). Ein
Semikolon am Ende schadet nicht. Fehler werden protokolliert, das Formular
öffnet trotzdem.
Fehlerbilder¶
| Symptom | Ursache |
|---|---|
| Liste leer | ein ${feld}-Platzhalter zeigt auf ein leeres Feld — oder die Quelle meldet einen Fehler (Backend-Log) |
| Fehler zu Tabellen- oder Spaltennamen | Namen mit unzulässigen Zeichen |
| SQL wird abgelehnt | sql beginnt nicht mit SELECT/WITH oder enthält INSERT/UPDATE/… |
| Skript-Lookup bricht ab | Skript braucht länger als 30 Sekunden — vereinfachen oder als Picker bauen |
| Vorbelegung greift nicht | onLoad setzt das Feld bereits, oder es ist kein neuer Datensatz |
Vollständiges Beispiel: Land → Bundesland¶
Zum Abschluss beide Felder zusammen in einem Abschnitt „Adresse": ein
Land-Feld mit alphabetisch sortierter Länderliste und daneben ein
Bundesland-Feld, das über ${land} an der Auswahl im Land-Feld hängt.
Beide sind Pflichtfelder.
Hinter den Kulissen: Abschnitt „Adresse" mit zwei verketteten Listen
{
"type": "object",
"title": "Adresse",
"kc:layout": "horizontal-2",
"properties": {
"land": {
"type": "string", "title": "Land",
"kc:lookup": { "table": "country", "keyColumn": "code",
"valueColumn": "label", "orderBy": "label" }
},
"bundesland": {
"type": "string", "title": "Bundesland",
"kc:lookup": { "table": "country_state", "keyColumn": "code",
"valueColumn": "label", "where": "country_code = ${land}",
"orderBy": "label" }
}
},
"required": ["land", "bundesland"]
}
Wechselt der Anwender das Land, lädt das Bundesland-Feld neu — du
beschreibst nur die Abhängigkeit im where.