Zum Inhalt

Sichtbarkeits- und Pflichtbedingungen

Ein Formular muss nicht alles auf einmal zeigen. Ein Feld kann erst auftauchen, wenn es gebraucht wird, ein anderes erst dann zur Pflicht werden, und wieder eines kann nur zum Lesen dastehen. Genau darum geht es hier: Du legst je Feld fest, wann es sichtbar ist, wann es ausgefüllt sein muss, wann es schreibgeschützt ist — und ob sein Wert verschwindet, sobald das Feld nicht mehr zu sehen ist. Alles davon ist freiwillig; ein Feld ohne solche Angaben ist immer da.

Die Bedingungen greifen sofort: Ein Klick auf ein Kästchen lässt das abhängige Feld erscheinen oder verschwinden, ohne dass das Formular nachlädt. Beim Speichern prüft der Server dieselben Bedingungen noch einmal nach — wer ein Feld im Browser künstlich einblendet, kommt damit also nicht durch.

Zwei Wege — einer davon ganz ohne Tippen

Am Feld selbst steht die Bedingung als kurzer Ausdruck, etwa inland_ausland === 'ausland'. Wer nicht tippen möchte, klickt sie stattdessen einmal als benannte Regel zusammen und setzt am Feld nur noch deren Namen ein — @Ausland. Das lohnt sich ohnehin, sobald dieselbe Bedingung an mehreren Feldern hängt.

Die eingeklappten Kästen „Hinter den Kulissen" auf dieser Seite zeigen, was dabei in das Formular geschrieben wird. Aufklappen lohnt sich, wenn du verstehen willst, warum etwas so funktioniert — zum Arbeiten brauchst du es nicht.

Im Formular-Editor

Im Eigenschaften-Dialog eines Feldes, Reiter Sichtbarkeit, stehen Sichtbarkeit, Pflichtfeld und Schreibgeschützt jeweils auf ein, aus oder bedingt. Bei bedingt erscheint darunter ein Eingabefeld für die Bedingung; ganz unten im selben Reiter sitzen die Schalter Wert leeren, sobald unsichtbar und Im PDF erscheinen.

Reiter „Sichtbarkeit" im Eigenschaften-Dialog des Feldes „zielland": Sichtbarkeit und Pflichtfeld stehen auf „bedingt" mit der Regel @Ausland, unten der Schalter „Wert leeren, sobald unsichtbar"

Das Bild zeigt das Feld zielland aus dem Beispiel Reisekostenabrechnung: Es erscheint nur bei einer Auslandsreise, ist dann Pflicht, und sein Wert verschwindet wieder, sobald es sich ausblendet. Alle drei Angaben verweisen auf dieselbe Regel @Ausland.

Beim Tippen hilft das Eingabefeld mit: Es schlägt vorhandene Regeln vor und hebt erkannte @Name-Referenzen grün hervor — ein Tippfehler im Regelnamen fällt damit sofort auf.

Klicken statt tippen: der Baukasten der Regeln

Der visuelle Bedingungs-Baukasten sitzt nicht am Feld, sondern bei den Regeln in den Formular-Eigenschaften. Dort klickst du eine Bedingung aus Bausteinen zusammen:

Bedingungsart Beispiel
Element (Wertvergleich) anrede ist gleich Firma
Benutzer / Gruppe / Berechtigung angemeldeter Benutzer ist in Gruppe Sachbearbeiter
Referenz auf andere Regel @IstFirma
Verschachtelte Gruppe (A oder B) und C
JavaScript freier Ausdruck

Bedingungen lassen sich mit UND/ODER verknüpfen und einzeln invertieren (NICHT). Der Baukasten schreibt daraus den fertigen Ausdruck, und am Feld genügt anschließend der Regelname.

Der Bedingungs-Baukasten einer Regel: Bausteine mit Feldauswahl und Vergleichswert, darunter der daraus erzeugte Ausdruck

Was der Anwender davon sieht

Im ausgefüllten Formular greift die Bedingung sofort. Solange Inland gewählt ist, gibt es das Feld Zielland nicht:

Formular mit Auswahl „Inland": das Feld Zielland fehlt

Mit der Auswahl Ausland erscheint es — und trägt den roten Stern, ist also auszufüllen:

Dasselbe Formular mit Auswahl „Ausland": rechts erscheint das Pflichtfeld Zielland

Bleibt es dann leer, meldet die Prüfung es an — die Pflicht gilt erst, seit das Feld sichtbar ist:

Das leere Feld Zielland mit rotem Rahmen und der Meldung „Pflichtfeld"

Was der Editor dabei hinterlegt

Was du im Reiter „Sichtbarkeit" zusammenklickst, notiert der Editor am Feld als eine der folgenden Eigenschaften. Du musst sie nie von Hand schreiben — aber sie zu kennen zahlt sich zweimal aus: Bedingungen reisen mit dem Formular mit (Export/Import), und wenn eine Regel nicht greift, zeigt ein Blick auf diese Eigenschaften, welcher Ausdruck tatsächlich am Feld hängt.

Attribut Wirkung Default
kc:visibleIf Dynamische Sichtbarkeit. Der Ausdruck wird live ausgewertet — sobald das Ergebnis truthy ist, wird das Feld gerendert; falsy versteckt es. sichtbar (kein Filter)
kc:requiredIf Dynamische Pflichtfeld-Markierung. Truthy → Feld bekommt das Sternchen und wird in der Validierung als Pflichtfeld behandelt. optional (kein Pflichtfeld)
kc:readonlyIf Dynamischer Schreibschutz. Truthy → Feld ist schreibgeschützt. beschreibbar
kc:clearIfInvisible Wenn true: sobald das Feld unsichtbar wird, setzt der Renderer den Wert automatisch auf null. Verhindert „Datenleichen" hinter ausgeblendeten Feldern. false (Wert bleibt erhalten)
kc:clearIfInvisibleIf wie kc:clearIfInvisible, aber nur unter einer Bedingung —
kc:printableIf Feld erscheint nur im PDF, wenn die Bedingung zutrifft druckbar

Statisch vs. dynamisch: Daneben gibt es kc:required: true (immer Pflicht) und kc:visible: false (immer unsichtbar). Beide schalten ihr Verhalten hart ein/aus, ohne Bedingung. Eine Kombination mit den dynamischen Varianten ist erlaubt — die statische Form gewinnt im Zweifel.

Wie eine Bedingung aufgebaut ist

Sowohl kc:visibleIf als auch kc:requiredIf erwarten einen JavaScript-Ausdruck als String. Der Wert wird ausgewertet und als Boolean interpretiert (truthy / falsy nach JS-Semantik mit ein paar formularfreundlichen Verfeinerungen).

Was steht im Ausdruck zur Verfügung?

  • Feldnamen direkt — jeder Eigenschaftsname aus properties ist als lokale Variable verfügbar (sofern der Name ein gültiger JS-Identifier ist).
  • values — das gesamte Werte-Objekt als read-only frozen Object. Nützlich für Feldnamen mit Punkten oder Sonderzeichen: values["adr.strasse"].
  • kc.num(v) — tolerantes Number-Parsing. "1,50" → 1.5, "1.234,56" → 1234.56, leerer String → 0, ungültige Eingabe → 0.
  • kc.isEmpty(v) — true für null, undefined, "" oder Whitespace-only.
  • kc.text(v) — null-sichere Trim-View, immer String.
  • ctx.attachments — die Dateianlagen des Datensatzes, z. B. ctx.attachments.length > 0.
  • @Regelname — eine benannte Regel; wird vor der Auswertung durch ihren Ausdruck ersetzt.

Operator-Übersicht

Operator Bedeutung Beispiel
|| Logisches ODER aktiv || archiv
&& Logisches UND verheiratet && hatKinder
=== Strikte Gleichheit anrede === "Firma"
!== Strikte Ungleichheit typ !== "intern"
== Lockere Gleichheit (Number ↔ String) alter == "18"
!= Lockere Ungleichheit plz != ""
< > <= >= Numerische Vergleiche betrag > 1000
! Negation !geschaeftskunde
( ) Gruppierung (a || b) && c
?. Optional Chaining plz?.startsWith("01")

Wann ein Wert als „wahr" gilt (formularfreundlich)

  • true / Checkbox angehakt → truthy
  • false / Checkbox nicht angehakt → falsy
  • Leerer String "" → falsy; nur Whitespace → falsy
  • Der String "false" → falsy (manche Backends liefern Booleans als String)
  • null / undefined → falsy
  • 0 → falsy; jede andere Zahl → truthy

Sicherheit und Sandboxing

Die Ausdrücke werden im Browser via new Function() ausgeführt — also echte ECMAScript-Auswertung mit den vollen Sprachfeatures. Damit dabei nichts schiefgeht, sind mehrere Schutzmaßnahmen aktiv:

  • Strict mode — 'use strict' steht im Function-Body. Implizite Globals wie foo = 5 (ohne let/const) werfen sofort.
  • values ist frozen — Object.freeze verhindert versehentliche Mutationen. values.feldname = "neu" wirft einen TypeError, der Ausdruck fällt auf den Default zurück.
  • this ist undefined — new Function setzt das beim Aufruf so.
  • Cache pro Ausdruck — kompilierte Funktionen werden gespeichert und auf Folge-Renders wiederverwendet. Kein Re-Parse pro Tastendruck.
  • Defensive Fallbacks — Parse- oder Laufzeitfehler werden geloggt (Browser-Konsole), das Feld bekommt seinen sicheren Default (true für visibleIf, false für requiredIf). Ein Tippfehler im Ausdruck legt nie das ganze Formular lahm.

Vertrauensgrenze

Schema-Autoren sind Admins/Entwickler — Endanwender schreiben keine Ausdrücke. Wer das Schema gestaltet, hat dieselbe Macht wie das Backend-onLoad-Skript. Ausdrücke unterliegen keinem zusätzlichen Whitelist-Filter, das ist bewusst so, weil es die kreative Nutzung nicht einschränken soll.

Die kc-Helfer im Detail

kc.num(v) — robuste Zahlenkonversion

Liefert eine finite Zahl, niemals NaN. Ideal für numerische Vergleiche an Eingabefeldern, die als String-Eingaben starten.

kc.num(42)         // → 42
kc.num("42")       // → 42
kc.num("1,50")     // → 1.5  (Komma als Dezimaltrenner)
kc.num("1.234,56") // → 1234.56  (der letzte Trenner ist das Dezimalzeichen)
kc.num("")         // → 0
kc.num(null)       // → 0
kc.num("abc")      // → 0

kc.num rechnet in Bedingungen genauso wie in Skripten (siehe kc-Helfer). Zahlenfelder (type: number) stehen Bedingungen ohnehin schon als echte Zahl bereit — auch wenn sie formatiert angezeigt werden; kc.num brauchst du vor allem für Textfelder.

kc.isEmpty(v) — null-/leer-Check

Behandelt null, undefined, leeren String und reinen Whitespace-String einheitlich als „leer". Booleans und Zahlen sind nie leer.

kc.isEmpty(null)        // → true
kc.isEmpty(undefined)   // → true
kc.isEmpty("")          // → true
kc.isEmpty("   ")       // → true
kc.isEmpty("Hallo")     // → false
kc.isEmpty(0)           // → false  (0 ist kein "leerer Wert")
kc.isEmpty(false)       // → false

kc.text(v) — Trim-View

Konvertiert beliebigen Wert in einen getrimmten String. Praktisch für Vergleiche bei Eingabefeldern, in denen Nutzer Leerzeichen tippen könnten.

kc.text(null)        // → ""
kc.text("  Hallo  ") // → "Hallo"
kc.text(42)          // → "42"
kc.text(true)        // → "true"

Fallbeispiele

Die folgenden Beispiele zeigen typische Muster aus realen Formularen. Jedes beschreibt zuerst, was der Anwender im fertigen Formular erlebt — der aufklappbare Kasten darunter zeigt dazu das Schema. Jedes Beispiel ist eigenständig; du kannst es direkt in dein Schema kopieren, wenn du lieber tippst als klickst.

Klassiker: Geburtsname nur bei Bedarf

Eine Checkbox steuert, ob das Geburtsname-Feld erscheint. Wenn der Nutzer die Checkbox abhakt, soll der Wert gelöscht werden — und nur wenn die Checkbox gesetzt ist, ist der Geburtsname Pflicht.

Im Editor: am Feld Geburtsname die Sichtbarkeit auf bedingt stellen und den Namen der Checkbox als Bedingung eintragen, dasselbe beim Pflichtfeld, und zusätzlich Wert leeren, sobald unsichtbar einschalten.

Hinter den Kulissen: Feld nur zeigen, wenn ein Kästchen angehakt ist
"geburtsnameVorhanden": {
  "type": "boolean",
  "title": "Geburtsname vorhanden"
},
"geburtsname": {
  "type": "string",
  "title": "Geburtsname",
  "kc:visibleIf":        "geburtsnameVorhanden",
  "kc:requiredIf":       "geburtsnameVorhanden",
  "kc:clearIfInvisible": true
}

Der Name der Checkbox steht als Bedingung schlicht für sich allein — der Wert ist bereits ein Boolean und muss mit nichts verglichen werden.

Anrede steuert Folgefelder

Bei der Anrede „Firma" werden andere Felder benötigt als bei einer natürlichen Person. Klassischer Wechsel zwischen zwei Feldgruppen: Vorname erscheint bei allen Anreden außer „Firma", Firmenname ausschließlich bei „Firma" — und das jeweils ausgeblendete Feld gibt seinen Wert ab.

Hinter den Kulissen: zwei Feldgruppen, die sich gegenseitig ablösen
"anrede": {
  "type": "string", "title": "Anrede",
  "enum": ["Herr", "Frau", "Divers", "Firma"]
},
"vorname": {
  "type": "string", "title": "Vorname",
  "kc:visibleIf":  "anrede !== 'Firma'",
  "kc:requiredIf": "anrede !== 'Firma'",
  "kc:clearIfInvisible": true
},
"firmenname": {
  "type": "string", "title": "Firmenname",
  "kc:visibleIf":  "anrede === 'Firma'",
  "kc:requiredIf": "anrede === 'Firma'",
  "kc:clearIfInvisible": true
}

Die beiden Bedingungen sind exakt gegenläufig (!== gegen ===) — damit ist immer genau eine der beiden Feldgruppen sichtbar.

Inland/Ausland — gegenseitig ausschließende Adressfelder

Je nach Wohnsitz braucht es unterschiedliche Adress-Detailfelder: bei inland das Bundesland, bei ausland den Staat. Der Renderer räumt jeweils das nicht-passende Feld weg.

Hinter den Kulissen: Auswahlfeld schaltet zwischen Bundesland und Staat
"wohnsitz": {
  "type": "string", "title": "Wohnsitz",
  "enum": ["inland", "ausland"]
},
"bundesland": {
  "type": "string", "title": "Bundesland",
  "kc:visibleIf":  "wohnsitz === 'inland'",
  "kc:requiredIf": "wohnsitz === 'inland'",
  "kc:clearIfInvisible": true
},
"staat": {
  "type": "string", "title": "Staat",
  "kc:visibleIf":  "wohnsitz === 'ausland'",
  "kc:requiredIf": "wohnsitz === 'ausland'",
  "kc:clearIfInvisible": true
}

Verglichen wird gegen die Werte aus enum — also gegen den gespeicherten Schlüssel, nicht gegen den angezeigten Text.

Mehrfach-Bedingungen mit && und ||

Komplexere Bedingungen — wenn beispielsweise ein Premium-Feature nur bei bestimmten Tarifen erscheinen soll, der Vertrag aber nicht gekündigt sein darf. Genauso lassen sich Telefon- und E-Mail-Feld an eine Kontaktart-Auswahl hängen, bei der „beides" beide Felder einblendet.

Als Regel zusammengeklickt entstehen solche Fälle im Baukasten als verschachtelte Gruppe: eine ODER-Gruppe für die Tarife, mit UND verknüpft mit der invertierten Kündigung.

Hinter den Kulissen: ODER-Gruppe mit UND kombiniert
"premiumOptionen": {
  "type": "string", "title": "Premium-Optionen",
  "kc:visibleIf": "(plan === 'pro' || plan === 'enterprise') && !kuendigung"
},
"kontaktTel": {
  "type": "string", "title": "Telefon",
  "kc:visibleIf":  "kontaktArt === 'telefon' || kontaktArt === 'beides'",
  "kc:requiredIf": "kontaktArt === 'telefon' || kontaktArt === 'beides'"
},
"kontaktEmail": {
  "type": "string", "title": "E-Mail",
  "kc:visibleIf":  "kontaktArt === 'email'   || kontaktArt === 'beides'"
}

Die Klammern sind bedeutsam: ohne sie würde && stärker binden als || und die Kündigung nur den letzten Tarif ausschließen.

Numerische Schwellenwerte

Wenn ein Eingabewert eine Grenze überschreitet, soll ein zusätzliches Begründungsfeld erscheinen. Ab 5.000 € wird ein Genehmiger verlangt, ab 10.000 € zusätzlich eine Begründung der Höhe. kc.num kümmert sich um String-zu-Number-Konversion bei Eingabefeldern.

Hinter den Kulissen: zwei gestaffelte Betragsgrenzen
"betrag": {
  "type": "number", "title": "Betrag",
  "kc:decimals": 2
},
"genehmiger": {
  "type": "string", "title": "Genehmiger",
  "kc:visibleIf":  "kc.num(betrag) > 5000",
  "kc:requiredIf": "kc.num(betrag) > 5000",
  "kc:clearIfInvisible": true
},
"begruendung": {
  "type": "string", "title": "Begründung der Höhe",
  "kc:widget": "textarea",
  "kc:visibleIf": "kc.num(betrag) >= 10000"
}

Zwei Felder mit unterschiedlichen Schwellen ergeben eine Staffel: ab 5.000 erscheint der Genehmiger, ab 10.000 kommt die Begründung dazu.

String-Methoden — Postleitzahl-basierte Logik

Da der Ausdruck echtes JavaScript ist, kannst du alle nativen String-Methoden nutzen. Das Beispiel blendet einen Hinweistext ein, sobald die Postleitzahl mit 01, 02 oder 04 beginnt, und eine Warnung, solange sie angefangen, aber noch nicht fünfstellig ist.

Dafür gibt es keinen Klickweg — die beiden Ausdrücke tippst du am Feld ein (oder in einer Regel unter der Bedingungsart JavaScript).

Hinter den Kulissen: Hinweisfelder, die an der Postleitzahl hängen
"plz": { "type": "string", "title": "Postleitzahl" },

"sachsenHinweis": {
  "type": "string", "title": "Hinweis für Sachsen",
  "kc:readonly": true,
  "kc:visibleIf": "plz?.startsWith('01') || plz?.startsWith('02') || plz?.startsWith('04')"
},
"plzWarnung": {
  "type": "string", "title": "PLZ scheint unvollständig",
  "kc:readonly": true,
  "kc:visibleIf": "plz && plz.length > 0 && plz.length < 5"
}

Das Fragezeichen in plz?.startsWith ist Optional Chaining: Ist das Feld noch leer, liefert der Ausdruck undefined statt eines Fehlers.

Minderjährigkeit + gesetzlicher Vertreter

Klassisches Behörden-Pattern: bei Minderjährigen muss ein gesetzlicher Vertreter erfasst werden — aber nur wenn die Vertretung nicht identisch mit dem Antragsteller ist. Erst das Häkchen Minderjährig bringt die Rückfrage nach der Identität, und erst deren Verneinung die beiden Vertreter-Felder.

Hinter den Kulissen: dreistufig aufbauende Bedingungen
"geburtsdatum":      { "type": "string", "title": "Geburtsdatum", "format": "date" },
"minderjaehrig":     { "type": "boolean", "title": "Minderjährig" },
"vertreterIdentisch": {
  "type": "boolean", "title": "Erziehungsberechtigter = Antragsteller",
  "kc:visibleIf": "minderjaehrig"
},
"vertreterName": {
  "type": "string", "title": "Name des gesetzlichen Vertreters",
  "kc:visibleIf":  "minderjaehrig && !vertreterIdentisch",
  "kc:requiredIf": "minderjaehrig && !vertreterIdentisch",
  "kc:clearIfInvisible": true
},
"vertreterAnschrift": {
  "type": "string", "title": "Anschrift des Vertreters",
  "kc:visibleIf":  "minderjaehrig && !vertreterIdentisch",
  "kc:requiredIf": "minderjaehrig && !vertreterIdentisch",
  "kc:clearIfInvisible": true
}

Dass dieselbe Bedingung an zwei Feldern steht, ist genau der Fall, für den sich eine benannte Regel lohnt (siehe unten).

Begründung bei Abweichung vom Standard

Wenn der Nutzer einen Rabatt einträgt oder eine abweichende Anschrift wählt, ist eine Begründung notwendig. Das Kommentarfeld bleibt dabei immer sichtbar — es wird lediglich zum Pflichtfeld.

Hinter den Kulissen: Pflichtfeld ohne Sichtbarkeits-Bedingung
"rabattProzent": {
  "type": "number", "title": "Rabatt (%)",
  "kc:decimals": 1
},
"abweichendeAnschrift": {
  "type": "boolean", "title": "Abweichende Lieferanschrift"
},
"kommentar": {
  "type": "string", "title": "Kommentar / Begründung",
  "kc:widget": "textarea",
  "kc:requiredIf": "kc.num(rabattProzent) > 0 || abweichendeAnschrift"
}

Hier steht bewusst nur kc:requiredIf und kein kc:visibleIf — das Feld ist immer da, bekommt aber je nach Lage das Pflicht-Sternchen.

Felder mit Punkt-im-Namen

Manche Feldnamen — etwa aus migrierten Systemen — enthalten Punkte. Sie sind keine gültigen JS-Identifier und müssen über values["..."] referenziert werden. Fachlich passiert hier nichts Neues: Ein Länder-Auswahlfeld blendet je nach Wahl das Kanton- oder das Bundesland-Feld ein.

Auch das ist ein Fall zum Tippen — in einer Regel wäre es die Bedingungsart JavaScript.

Hinter den Kulissen: Feldnamen über values[...] ansprechen
"adr.land": {
  "type": "string", "title": "Land",
  "enum": ["DE", "AT", "CH"]
},
"adr.kanton": {
  "type": "string", "title": "Kanton (CH)",
  "kc:visibleIf":  "values['adr.land'] === 'CH'",
  "kc:requiredIf": "values['adr.land'] === 'CH'",
  "kc:clearIfInvisible": true
},
"adr.bundesland": {
  "type": "string", "title": "Bundesland (AT)",
  "kc:visibleIf":  "values['adr.land'] === 'AT'",
  "kc:clearIfInvisible": true
}

values ist das komplette Werte-Objekt. Der Zugriff in eckigen Klammern funktioniert für jeden Feldnamen — auch für solche mit Leerzeichen oder Umlauten.

Plausibilitäts-Hinweis (read-only Info-Feld)

Ein read-only-Feld als Hinweistext, das nur erscheint, wenn eine Inkonsistenz erkannt wird — hier eine Miete von mehr als 40 % des Monatseinkommens.

Hinter den Kulissen: Warnhinweis aus einem Verhältnis zweier Zahlen
"einkommen": { "type": "number", "title": "Monatseinkommen" },
"miete":     { "type": "number", "title": "Monatsmiete" },

"warnungUebermiete": {
  "type": "string",
  "title": "⚠️ Mietbelastung sehr hoch",
  "kc:readonly": true,
  "kc:visibleIf": "kc.num(einkommen) > 0 && kc.num(miete) / kc.num(einkommen) > 0.4"
}

Der erste Teil der Bedingung (einkommen > 0) verhindert eine Division durch null, solange das Einkommen noch nicht erfasst ist.

Zahlungsweise → IBAN/Karte je nach Auswahl

Klassisches Bezahl-Pattern: bei Lastschrift sind IBAN und Kontoinhaber Pflicht, bei Kreditkarte die Kartennummer, bei Rechnung gar nichts Zusätzliches.

Hinter den Kulissen: drei Zahlungswege mit unterschiedlichen Folgefeldern
"zahlungsweise": {
  "type": "string", "title": "Zahlungsweise",
  "enum": ["rechnung", "lastschrift", "kreditkarte"],
  "kc:required": true
},
"iban": {
  "type": "string", "title": "IBAN",
  "kc:visibleIf":  "zahlungsweise === 'lastschrift'",
  "kc:requiredIf": "zahlungsweise === 'lastschrift'",
  "kc:clearIfInvisible": true
},
"kontoinhaber": {
  "type": "string", "title": "Kontoinhaber",
  "kc:visibleIf":  "zahlungsweise === 'lastschrift'",
  "kc:requiredIf": "zahlungsweise === 'lastschrift'",
  "kc:clearIfInvisible": true
},
"kartennummer": {
  "type": "string", "title": "Kreditkartennummer",
  "kc:visibleIf":  "zahlungsweise === 'kreditkarte'",
  "kc:requiredIf": "zahlungsweise === 'kreditkarte'",
  "kc:clearIfInvisible": true
}

Für rechnung ist schlicht kein Folgefeld definiert — die Auswahl blendet dann alle drei Zusatzfelder aus und räumt ihre Werte weg.

Datums-Vergleich

Datums-Strings im ISO-Format können direkt mit < oder > verglichen werden, weil ISO-8601 lexikographisch sortierbar ist. Im Beispiel erscheint ein Hinweis, sobald das Geburtsdatum nach dem 1.1.2008 liegt.

Hinter den Kulissen: Hinweis ab einem festen Stichtag
"geburtsdatum": {
  "type": "string", "title": "Geburtsdatum",
  "format": "date"
},
"hinweisVertreter": {
  "type": "string",
  "title": "⚠️ Antragsteller scheint minderjährig — gesetzlichen Vertreter ergänzen",
  "kc:readonly": true,
  "kc:visibleIf": "geburtsdatum && geburtsdatum > '2008-01-01'"
}

Der vorangestellte Test auf geburtsdatum sorgt dafür, dass der Hinweis bei noch leerem Feld nicht aufpoppt.

Soll die Grenze mitwandern („heute minus 18 Jahre"), rechnest du sie im Ausdruck selbst aus — es ist normales JavaScript:

"kc:visibleIf": "geburtsdatum && geburtsdatum > new Date(new Date().setFullYear(new Date().getFullYear() - 18)).toISOString().slice(0, 10)"

Für mehrfach gebrauchte Grenzen lohnt sich eine Regel.

Aufbauende Bedingungen (Folge-Schritte)

Mehrstufige Formulare wie ein Wizard — Schritt 2 wird erst sichtbar, wenn Schritt 1 ausgefüllt ist, Schritt 3 erst wenn Schritt 2 vollständig ist. Das Formular wächst also mit, während der Anwender es ausfüllt.

Hinter den Kulissen: Felder, die sich nacheinander freischalten
"schritt1Email": {
  "type": "string", "title": "Schritt 1: E-Mail",
  "format": "email", "kc:required": true
},
"schritt2Name": {
  "type": "string", "title": "Schritt 2: Name",
  "kc:visibleIf":  "!kc.isEmpty(schritt1Email)",
  "kc:requiredIf": "!kc.isEmpty(schritt1Email)"
},
"schritt3Anliegen": {
  "type": "string", "title": "Schritt 3: Ihr Anliegen",
  "kc:widget": "textarea",
  "kc:visibleIf":  "!kc.isEmpty(schritt1Email) && !kc.isEmpty(schritt2Name)",
  "kc:requiredIf": "!kc.isEmpty(schritt1Email) && !kc.isEmpty(schritt2Name)"
}

!kc.isEmpty(...) statt eines nackten Feldnamens ist hier wichtig: Ein Textfeld, in das nur Leerzeichen getippt wurden, gilt so weiterhin als unausgefüllt.

Statisch + dynamisch kombiniert

Manche Felder sind grundsätzlich Pflicht, andere Bedingungen kommen je nach Kontext dazu. Die statische Form gewinnt — wenn kc:required: true gesetzt ist, ist das Feld immer Pflicht, kc:requiredIf wird ignoriert.

Hinter den Kulissen: hartes Pflichtfeld neben einem bedingten
"email": {
  "type": "string", "title": "E-Mail",
  "format": "email",
  "kc:required": true
},
"emailAlternativ": {
  "type": "string", "title": "Alternative E-Mail",
  "format": "email",
  "kc:visibleIf":  "zweiteEmailGewuenscht",
  "kc:requiredIf": "zweiteEmailGewuenscht",
  "kc:clearIfInvisible": true
}

Im Editor entspricht das den Schalterstellungen ein (hart) beim ersten und bedingt beim zweiten Feld.

Best Practices

Bedingungs-Symmetrie für Pflicht und Sichtbarkeit

In den meisten Fällen ist die Bedingung für „sichtbar" auch die Bedingung für „Pflicht". Halte sie identisch — sonst wäre ein Pflichtfeld unsichtbar (Nutzer wissen nicht, warum sie nicht abschicken können) oder ein sichtbares Feld plötzlich nicht mehr Pflicht.

Setze beide Attribute mit demselben Ausdruck und ergänze kc:clearIfInvisible: true, wenn das Feld semantisch keinen Wert tragen soll, solange es nicht sichtbar ist.

kc:clearIfInvisible nutzen, um Datenleichen zu vermeiden

Ohne dieses Flag bleibt der Wert eines Feldes erhalten, auch wenn es ausgeblendet wird. Beim nächsten Submit landet er in der Datenbank — und wenn der Nutzer die Trigger-Checkbox später wieder aktiviert, taucht der alte Wert wieder auf. Das ist manchmal gewünscht (Notizen für später aufheben), oft aber unerwartet.

Faustregel: immer clearIfInvisible setzen, wenn das Feld semantisch nur in einem bestimmten Zustand existiert — z.B. Geburtsname, Vertreter-Daten, Auslands-Felder.

Komplexe Bedingungen lieber benennen

Wenn dieselbe Bedingung in mehreren Feldern auftaucht, definiere sie einmal als Regel und referenziere sie mit @Name. Am Feld steht dann statt des ausgeschriebenen Vergleichs nur noch der sprechende Regelname.

Hinter den Kulissen: eine benannte Regel, an drei Feldern verwendet
"kc:rules": [
  { "name": "IstFirma", "expr": "anrede === 'Firma'" }
],
"firmenname":   { "kc:visibleIf": "@IstFirma" },
"firmensitz":   { "kc:visibleIf": "@IstFirma" },
"firmensteuer": { "kc:visibleIf": "@IstFirma" }

Die Regeln stehen einmal zentral unter kc:rules; die Felder verweisen mit @Name darauf.

Das macht spätere Anpassungen leichter — du pflegst die Bedingung nur an einer Stelle, und @IstFirma ist mit weiterer Logik kombinierbar (@IstFirma && kc.num(umsatz) > 0). Details unter Regeln.

Ausdrücke kompakt halten

Lange Verknüpfungen in einem einzelnen Ausdruck werden schnell unleserlich. Zerlege sie in Regeln mit sprechenden Namen und setze sie mit @Name zusammen — der Baukasten der Regeln stellt verschachtelte Gruppen übersichtlich dar.

Testen

Schreibe die Bedingung, speichere, und prüfe sie in der Editor-Vorschau: Die Vorschau wertet Bedingungen live aus. Mit Ausgeblendete anzeigen siehst du auch Felder, die gerade unsichtbar sind — sie erscheinen ausgegraut, mit gestricheltem Rahmen und dem Vermerk bedingt sichtbar. So kannst du sie anklicken und weiterbearbeiten, ohne die Bedingung erfüllen zu müssen.

Editor-Vorschau mit gesetztem Haken „Ausgeblendete anzeigen": das Feld „Zielland" erscheint ausgegraut mit dem Vermerk „bedingt sichtbar"

Fehlerhafte Ausdrücke meldet die Browser-Konsole („Ausdruck konnte nicht ausgewertet werden").

Limitierungen und Hinweise

  • Serverseitige Prüfung — beim Speichern wertet der Server kc:visibleIf und kc:requiredIf selbst aus (gleiche Semantik wie im Browser): Unsichtbare Felder werden nicht geprüft, sichtbare mit zutreffendem kc:requiredIf müssen gefüllt sein. Schreibgeschützte Felder sind nie Pflicht.
  • kc:clearIfInvisible nur im Browser — das Leeren übernimmt der Renderer. Wer Daten per API oder Skript ohne das Web-Formular schreibt, muss unsichtbare Felder selbst leeren.
  • Versteckte Werte beim Laden — wenn ein gespeicherter Datensatz Werte für aktuell unsichtbare Felder enthält und kc:clearIfInvisible aktiv ist, werden diese beim ersten Render gelöscht. Beim Laden eines Datensatzes „wandern" diese Werte also einmalig zu null und beim nächsten Submit ist die DB konsistent — semantisch genau das, was wir wollen.
  • Performance — Ausdrücke werden bei jedem Render neu evaluiert. Der Cache verhindert Re-Compile, aber die Auswertung selbst läuft auf jedem Tastendruck im jeweils ausgewerteten Feld. Bei einem typischen Formular mit 20 Bedingungen liegt das im Mikrosekunden-Bereich und ist nicht spürbar.
  • Kein abgeschotteter Sandkasten im Browser — der Ausdruck könnte window oder document ansprechen, weil new Function im globalen Bereich läuft. Da nur Formular-Autoren Ausdrücke schreiben, ist das bewusst so.

Schnellreferenz

Was du erreichen willst So machst du es
Feld nur zeigen, wenn Checkbox X gesetzt ist "kc:visibleIf": "x"
Feld nur zeigen, wenn Checkbox X NICHT gesetzt ist "kc:visibleIf": "!x"
Feld nur zeigen, wenn Auswahl A "Wert" hat "kc:visibleIf": "a === 'Wert'"
Feld zeigen bei einem von mehreren Werten "kc:visibleIf": "typ === 'A' || typ === 'B'"
Feld zeigen NUR wenn beide Bedingungen wahr sind "kc:visibleIf": "x && y"
Feld zeigen, wenn Zahl > 100 "kc:visibleIf": "kc.num(betrag) > 100"
Pflichtfeld nur wenn sichtbar (Standard-Pattern) beide Attribute auf denselben Ausdruck setzen
Wert beim Ausblenden auto-leeren "kc:clearIfInvisible": true
Punkt im Feldnamen referenzieren values["adr.land"]
Optional Chaining für eventuell-leere Strings plz?.startsWith("01")