Datenelemente: Werte, Typen, Zeilen¶
Jedes Feld, das du im Formular-Editor anlegst, ist ein Datenelement. Im Skript begegnet es dir ständig: Du liest seinen Wert, rechnest damit, schreibst etwas zurück, prüfst ihn oder gehst über alle Felder eines Formulars.
Diese Seite zeigt das Handwerkszeug dafür — von „Wie komme ich an den Wert?" bis „Wie merke ich, dass sich etwas geändert hat?". Ohne Vorwissen, in kleinen Schritten. Die Sprache selbst steht nebenan unter JavaScript-Grundlagen, Objekte und Listen unter JSON und Objekte verstehen.
Zum Mitmachen
Alle Beispiele laufen in der Skript-Konsole: System-Administration →
Werkzeuge & Diagnose → Skript-Konsole. Beispiel hineinkopieren,
Ausführen klicken, unten die Ausgabe lesen. log.info(...) schreibt
eine Zeile in diese Ausgabe.
Ein Datenelement hat zwei Seiten¶
Stell dir ein Feld wie ein beschriftetes Fach in einem Schrank vor. Es gibt
- das Fach selbst: Es heißt
betrag, trägt die Beschriftung „Rechnungsbetrag", nimmt nur Zahlen auf und ist vielleicht ein Pflichtfeld. Das ändert sich nicht, solange niemand das Formular umbaut. - den Inhalt: gerade steht
249.90darin. Der wechselt bei jedem Datensatz.
Im Skript sind das zwei verschiedene Zugänge — und das ist die wichtigste Unterscheidung auf dieser Seite:
| Ich möchte … | … dafür nimmst du |
|---|---|
| den Wert lesen oder ändern | ctx.form.<name> |
| wissen, was für ein Feld das ist (Typ, Beschriftung, Pflicht) | ctx.model.primary.elements.<name> |
| über alle Felder laufen | ctx.model.primary.elements |
| Zeilen einer Detail-Tabelle bearbeiten | ctx.form.<tabellenfeld> |
| einen Fehler an ein Feld hängen | field.<name>.error(...) |
Merksatz: ctx.form ist der Inhalt, ctx.model ist die Beschriftung des
Fachs. Geschrieben wird immer in ctx.form — ctx.model ist nur zum
Nachschlagen da.
1. Den Wert lesen und schreiben¶
Der Wert steht in ctx.form, unter dem Namen des Feldes:
// lesen
var nachname = ctx.form.name;
// schreiben
ctx.form.ergebnis = 'Hallo ' + nachname;
log.info(ctx.form.ergebnis); // Hallo Berg
Was du in ctx.form schreibst, übernimmt das Formular anschließend sichtbar —
genau so baut man Berechnungen, Vorbelegungen und automatisch gefüllte Felder.
Drei Schreibweisen für dasselbe¶
KCForms bietet dir drei Wege zum selben Wert. Sie sind gleichwertig; nimm den, der sich für dich am besten liest:
ctx.form.name = 'Berg'; // ausführlich
$vorname = 'Anna'; // Kurzform mit $
field.email.value = 'a.berg@example.com'; // Feld-Handle
log.info($name + ', ' + ctx.form.vorname + ', ' + field.email.value);
| Schreibweise | Wann sie sich lohnt |
|---|---|
ctx.form.betrag |
immer richtig |
$betrag |
kürzeste Form für schnelle Rechnungen |
field.betrag.value |
wenn du am selben Feld auch Fehler meldest (siehe Abschnitt 8) |
Wenn der Feldname erst zur Laufzeit feststeht¶
Alle drei Schreibweisen können den Namen auch aus einer Variablen nehmen — eckige Klammern statt Punkt:
var summe = 0;
for (var i = 1; i <= 2; i++) {
summe += $["wert" + i].num; // Kurzform
}
log.info("Summe: " + kc.numFormat(summe, null, null, 0, 2));
var feld = "summe";
ctx.form[feld] = summe; // ausführlich
field[feld].value = summe; // Feld-Handle
Das ist der übliche Weg bei durchnummerierten Feldern — beitrag1 bis
beitrag12, position1 bis position5 und so weiter.
Ein Name, den es nicht gibt, bricht ab
$["beitrga1"] meldet sofort
$["beitrga1"]: Es gibt kein Datenelement mit diesem Namen. — genauso
laut, wie sich $beitrga1 als ReferenceError meldet. Das ist Absicht:
Eine Schleife, die einen Schritt zu weit läuft, würde sonst ab da still
mit 0 weiterrechnen, und das fällt erst am Ergebnis auf.
Willst du bewusst prüfen, ob es ein Feld gibt, geht das ohne Abbruch:
Oder mit ctx.form["quatsch"] — das liefert undefined statt eines Fehlers.
2. Die häufigste Stolperfalle: Text sieht aus wie eine Zahl¶
Alles, was aus dem Browser kommt, ist erst einmal Text — auch dann, wenn im
Schema number steht. Rechnet man damit direkt, klebt JavaScript die Texte
aneinander, statt zu addieren:
ctx.form.menge = '3';
ctx.form.preis = '4';
log.info('Falsch: ' + ($menge + $preis)); // 34
log.info('Richtig: ' + ($menge.num + $preis.num)); // 7
Deshalb: vor jeder Rechnung .num anhängen. Das verkraftet auch leere
Felder (0), deutsche Kommazahlen ("1.234,56") und Unsinn (0 statt
Absturz).
ctx.form.number = '1.234,56';
log.info('Als Text: ' + $number); // 1.234,56
log.info('Als Zahl: ' + $number.num); // 1234.56
.num gibt es an jedem deklarierten Feld — als $feld.num und als
field.feld.num. Details und die Begründung, warum der Wert überhaupt Text
ist, unter kc-Helpers und im
ctx.form-Abschnitt.
Wann kc.num(...)
.num gibt es nur an deklarierten Feldern und nur in Skripten.
Überall sonst macht kc.num(...) dasselbe und ist dort die einzige
Möglichkeit: für Zwischenergebnisse, Werte aus db.query(…), Zeilen einer
Detail-Tabelle oder CSV-Datei — und in Bedingungen (kc:visibleIf, Regeln)
sowie in kc:calc.
Nicht an ctx.form.<feld> hängen: Ein nie ausgefülltes Feld ist dort
null, und null.num bricht ab. Ausgeschrieben also
kc.num(ctx.form.betrag).
Und wieder zurück: die Zahl anzeigen¶
.num ist der Weg zum Rechnen. Für den Rückweg — Zahl zu Text, so wie sie
ein Mensch lesen soll — gibt es .numFormat(…):
log.info($betrag.numFormat(null, "€", 12, 2, ",", ".", 3)); // 12.345,76 €
log.info($betrag.numFormat("EUR", null, 0, 2)); // EUR 12345,76
Die Reihenfolge der sieben Angaben ist prefix, suffix,
stellenInsgesamt, anzNachkommastellen, dezimaltrenner,
separatorZeichen, separatorBlockabstand; alle sind optional, null
überspringt eine Position. Die vollständige Beschreibung samt Obergrenze und
Rundung steht unter .numFormat.
Warum nicht toFixed(2)
toFixed ist JavaScript und kennt nur den Punkt als Dezimaltrenner —
aus 1234.5 wird "1234.50", und für einen deutschen Beleg steht dann ein
englisch geschriebener Betrag im Formular. Der übliche Ausweg
.toFixed(2).replace(".", ",") bekommt zwar das Komma hin, aber keine
Tausendergruppen, und er wirft bei einem Feldwert aus dem Browser einen
Fehler: toFixed gibt es nur an einer Zahl, und dort steht Text.
3. Ist das Feld überhaupt ausgefüllt?¶
log.info('suchbegriff: ' + (kc.isEmpty(ctx.form.suchbegriff) ? 'leer' : 'gefüllt'));
log.info('land: ' + (kc.isEmpty(ctx.form.land) ? 'leer' : 'gefüllt'));
kc.isEmpty ist wahr für „nichts eingetragen": null, leerer Text, nur
Leerzeichen. Nicht leer sind dagegen 0 und false — eine Menge von 0 und
ein bewusst abgewähltes Häkchen sind gültige Eingaben.
Für ein deklariertes Feld geht es wieder kürzer — direkt am Wert oder über das Handle, beide beantworten dieselbe Frage:
log.info('suchbegriff: ' + ($suchbegriff.isEmpty ? 'leer' : 'gefüllt'));
log.info('land: ' + (field.land.isEmpty ? 'leer' : 'gefüllt'));
Ein Feld, das noch nie ausgefüllt wurde, steht in der Datenbank auf NULL —
$doktorgrad.isEmpty ist dann wahr. Das liegt daran, dass der $-Alias
null als leeren Text liest; ctx.form.doktorgrad bleibt null und die
Spalte damit ebenfalls.
Nicht mit if (ctx.form.menge) prüfen
if (ctx.form.menge) ist auch dann falsch, wenn 0 drinsteht — das ist ein
Klassiker unter den schwer zu findenden Fehlern. Nimm $menge.isEmpty,
field.menge.isEmpty oder kc.isEmpty(...).
isEmpty ohne Klammern
$menge.isEmpty und field.menge.isEmpty sind Eigenschaften, keine
Methoden — also ohne (). Das ist Absicht: Schreibst du versehentlich
field.menge.isEmpty(), bekommst du sofort einen Fehler. Wäre es umgekehrt
eine Methode, wäre if (field.menge.isEmpty) immer wahr (eine Funktion
ist truthy) — und das wäre genau derselbe stille Fehler, vor dem der Kasten
darüber warnt.
4. Was für ein Feld ist das? Typ und Beschreibung auslesen¶
Für alles über das Feld selbst gibt es ctx.model. Die Felder des aktuellen
Formulars stehen unter ctx.model.primary.elements:
var el = ctx.model.primary.elements.betrag;
log.info('Name: ' + el.id); // betrag
log.info('Beschriftung: ' + el.label); // der Titel aus dem Editor — oder der Name, wenn keiner gesetzt ist
log.info('Typ: ' + el.type); // number
log.info('Pflichtfeld: ' + el.required); // false
Das sind die Angaben aus dem Formular-Editor. Typische Werte für type sind
string, number, integer, date und boolean.
Noch genauer: def und column¶
Jedes Element hat zwei weitere Zugänge — die Schema-Sicht und die Datenbank-Sicht:
var el = ctx.model.primary.elements.betrag;
// .def — die Definition aus dem Formular-Editor
log.info('Schema-Typ: ' + el.def.type);
log.info('Nachschlage-Liste: ' + el.def['kc:lookup']); // undefined, wenn keine gesetzt ist
// .column — wie das Feld in der Datenbank steht
if (el.column) {
log.info('Spalte: ' + el.column.name);
log.info('Spaltentyp: ' + el.column.ddl); // decimal(18,4)
log.info('darf leer sein: ' + el.column.nullable);
}
column kann null sein
Schaltflächen, Texte und Überschriften sind zwar Elemente, haben aber keine
Spalte in der Datenbank. Bei ihnen ist column gleich null — deshalb steht
oben das if (el.column) davor. Ohne diese Prüfung bricht das Skript mit
„Cannot read property" ab, sobald ein solches Element dazukommt.
el.def lässt sich nicht als Ganzes ausgeben
log.info(JSON.stringify(el.def)) zeigt nur {}. Frag die Einträge einzeln
ab (el.def.type, el.def['kc:lookup']) — das funktioniert.
Gibt es das Feld überhaupt?¶
Ein unbekannter Name liefert undefined statt eines Fehlers. Wenn der Name aus
einer Variablen kommt, prüf das lieber:
var el = ctx.model.primary.elements['gibtsnicht'];
if (!el) {
log.info('Das Feld kenne ich nicht.');
} else {
log.info('Typ: ' + el.type);
}
5. Über alle Datenelemente laufen¶
elements ist gleichzeitig eine Liste und ein Verzeichnis. Für eine Schleife
nimmst du die Listen-Seite: length sagt, wie viele es sind, [i] holt das
i-te Element.
var p = ctx.model.primary;
log.info('Tabelle: ' + p.table);
log.info('Felder insgesamt: ' + p.elements.length);
for (var i = 0; i < p.elements.length; i++) {
var el = p.elements[i];
log.info(el.id + ' (' + el.type + ') = ' + ctx.form[el.id]);
}
Beachte die letzte Zeile: el.id ist der Name, und mit
ctx.form[el.id] holst du dazu den Wert. So verbindest du beide Seiten —
das ist das Muster hinter fast allen Skripten, die „über alles" laufen.
Die sichere Variante: get(...)
Neben der eckigen Klammer und dem Punkt gibt es get(...) — mit einer Zahl
für die Position oder mit dem Namen:
ctx.model.primary.elements.get(0); // nach Position
ctx.model.primary.elements.get('betrag'); // nach Namen
Das Ergebnis ist dasselbe. get(...) ist aber die sichere Wahl, wenn der
Feldname aus einer Variablen kommt — und in einem Fall sogar die einzig
richtige: An der Sammlung selbst sind list, length, get und ids
belegt. Heißt ein Feld zufällig so, liefert elements.length die Anzahl
statt des Feldes. elements.get('length') trifft das Feld.
Nur die Namen brauchst du? Dafür gibt es ids():
var namen = ctx.model.primary.elements.ids();
log.info(namen.join(', ')); // name, vorname, email, betrag, …
Beispiel: alle leeren Pflichtfelder melden¶
Ein Skript, das ohne Änderung in jedem Formular funktioniert — es fragt das Modell, statt Feldnamen fest einzutippen:
// onValidate
var p = ctx.model.primary;
var fehlend = [];
for (var i = 0; i < p.elements.length; i++) {
var el = p.elements[i];
if (el.required && kc.isEmpty(ctx.form[el.id])) {
fehlend.push(el.label || el.id);
ctx.addError(el.id, 'Bitte ausfüllen.');
}
}
log.info('Fehlende Pflichtfelder: ' + (fehlend.length ? fehlend.join(', ') : 'keine'));
6. Änderungen erkennen¶
„Hat sich etwas geändert?" kann dreierlei heißen. Nimm den Abschnitt, der zu deiner Frage passt.
6.1 Welches Feld hat der Benutzer gerade verlassen?¶
Im Hook onFieldChange steht der Name des auslösenden Feldes in
ctx.form._changedField:
// onFieldChange
var geaendert = ctx.form._changedField;
if (!geaendert) {
log.info('Läuft gerade nicht wegen eines einzelnen Feldes.');
} else if (geaendert === 'land') {
ctx.form.kundennr = ''; // Land gewechselt → Kundennummer passt nicht mehr
log.info('Land geändert, Kundennummer geleert.');
}
_changedField ist nur während des Hooks da; gespeichert wird es nie.
Lieber neu rechnen als vergleichen
Für Summen und abhängige Werte brauchst du _changedField meist gar nicht.
Rechne einfach aus dem gesamten aktuellen Stand neu — dann stimmt das
Ergebnis auch, wenn mehrere Felder auf einmal ankommen:
6.2 Ist das ein neuer Datensatz?¶
if (ctx.instanceId === null) {
log.info('Dieser Datensatz wurde noch nie gespeichert.');
} else {
log.info('Bestehender Datensatz Nr. ' + ctx.instanceId);
}
In den Hooks rund ums Speichern (onSubmit, onAfterSave) gibt es zusätzlich
ctx.isNew: true, wenn der Satz gerade neu angelegt wurde, false bei einer
Änderung an einem bestehenden.
6.3 Hat sich ein Wert gegenüber dem gespeicherten Stand geändert?¶
Diese Frage beantwortet KCForms nicht von allein — den alten Wert kennt nur die Datenbank. Du liest ihn dir und vergleichst selbst:
// onBeforeSave — prüfen, ob der Betrag angefasst wurde
if (ctx.instanceId === null) {
log.info('Neuer Datensatz — noch nichts zu vergleichen.');
} else {
var alt = db.queryOne(
'SELECT betrag FROM ' + ctx.model.primary.table + ' WHERE id = ${id}',
{ id: ctx.instanceId });
if (alt && kc.num(alt.betrag) !== kc.num(ctx.form.betrag)) {
log.info('Betrag geändert: ' + alt.betrag + ' → ' + ctx.form.betrag);
}
}
Zwei Dinge sind hier absichtlich so gebaut:
- Der Tabellenname kommt aus
ctx.model.primary.table, nicht als getippter Text. Benennt jemand das Formular um, läuft das Skript weiter. - Verglichen wird mit
kc.num(...), nicht direkt. Sonst gilt"100"als verschieden von100, und du meldest eine Änderung, die keine war.
7. Zeilen: Datenelemente in einer Detail-Tabelle¶
Eine Detail-Tabelle ist ein Feld, in dem statt eines einzelnen Werts viele Zeilen stecken. Im Skript ist das eine Liste von Objekten: ein Objekt je Zeile, darin die Spalten als ganz normale Datenelemente.
var zeilen = ctx.form.positionen || []; // || [] schützt vor "noch keine Zeilen"
var gesamt = 0;
for (var i = 0; i < zeilen.length; i++) {
var zeile = zeilen[i];
if (!zeile) continue; // Leerzeilen überspringen
zeile.zeilensumme = kc.num(zeile.menge) * kc.num(zeile.preis);
gesamt += zeile.zeilensumme;
log.info('Zeile ' + (i + 1) + ': ' + zeile.menge + ' × ' + zeile.preis
+ ' = ' + zeile.zeilensumme);
}
ctx.form.gesamtwert = gesamt;
log.info('Gesamt: ' + gesamt);
Das Muster ist immer dasselbe:
- Liste aus
ctx.formholen und mit|| []absichern. - Mit einer Schleife durchgehen, leere Einträge überspringen.
- Innerhalb der Zeile ganz normal auf die Spalten zugreifen:
zeile.menge. - Ergebnisse in die Zeile oder ins Hauptformular zurückschreiben.
Zeilen ändern: ja. Zeilen anlegen: nein.
Bestehende Zeilen kannst du bedenkenlos ändern. Neue Zeilen per
zeilen.push({...}) anzuhängen oder die Liste durch ein neues Array zu
ersetzen, funktioniert derzeit nicht zuverlässig.
Für einfache Rechnungen brauchst du kein Skript
Zeilensumme und Gesamtsumme kann KCForms selbst — siehe Berechnungsfelder. Zum Skript greifst du, wenn Bedingungen, Nachschlagen oder Prüfungen dazukommen.
Welche Zeile war gemeint?¶
Steht in einer Zeile eine Schaltfläche, bekommt der Hook die Zeilennummer in
der globalen Variablen event mit — 1 für die erste Zeile, 2 für die zweite und
so weiter. Damit holst du dir genau die angeklickte Zeile:
// Hook einer Schaltfläche in der Spalte einer Detail-Tabelle
var zeilen = ctx.form.positionen || [];
var nr = Number(event); // 1-basiert!
var zeile = zeilen[nr - 1]; // Listen zählen ab 0 → minus 1
if (zeile) {
zeile.geprueft = 'ja';
log.info('Zeile ' + nr + ' geprüft.');
}
Daneben steht in ctx.form._triggeredHook der Name des Hooks, der gerade läuft
— praktisch, wenn mehrere Schaltflächen denselben Hook aufrufen.
Die Struktur einer Detail-Tabelle nachschlagen¶
Auch Detail-Tabellen haben eine Modell-Sicht. Über ctx.model.children kommst
du an Tabellennamen und Spalten — zum Lesen, nicht zum Schreiben:
log.info('Detail-Tabellen: ' + ctx.model.children.length);
for (var i = 0; i < ctx.model.children.length; i++) {
var ds = ctx.model.children[i];
log.info(ds.id + ' → Tabelle ' + ds.table + ', ' + ds.elements.length + ' Spalten');
for (var j = 0; j < ds.elements.length; j++) {
log.info(' ' + ds.elements[j].id + ' (' + ds.elements[j].type + ')');
}
}
ctx.model ist nur zum Lesen
Werte, die du über ctx.model…rows[…].elements.<x>.value setzt, landen
nicht im Datensatz und werden nicht gespeichert. Geschrieben wird immer
über ctx.form.
8. Einen Fehler an ein Datenelement hängen¶
Damit eine Meldung direkt am richtigen Feld erscheint, gibst du den Feldnamen
mit. Das field-Handle ist dafür die kürzeste Schreibweise:
// onValidate
if (kc.num(ctx.form.betrag) < 0) {
field.betrag.error('Der Betrag darf nicht negativ sein.');
log.info('Meldung gesetzt.');
}
| Aufruf | Wirkung |
|---|---|
field.betrag.error('…') |
weicher Fehler — Speichern kann erlaubt bleiben |
field.betrag.hardError('…') |
harter Fehler — blockiert |
ctx.addError('betrag', '…') |
dasselbe wie error, mit Namen als Text |
Die Langform ctx.addError(name, text) brauchst du, wenn der Feldname in einer
Variablen steht — so wie in der Pflichtfeld-Schleife in Abschnitt 5.
9. Kleine Helfer zum Mitnehmen¶
Diese vier Funktionen decken die meisten Alltagsfälle ab. Leg sie unter Hilfsfunktionen ab (im Skript-Editor neben den Hooks), dann kann jeder Hook dieses Formulars sie benutzen.
/** Wert eines Feldes als Zahl — egal ob Text, leer oder Kommazahl. */
function zahl(feldname) {
return kc.num(ctx.form[feldname]);
}
/** Wahr, wenn das Feld nicht ausgefüllt ist. */
function leer(feldname) {
return kc.isEmpty(ctx.form[feldname]);
}
/** Summe einer Spalte über alle Zeilen einer Detail-Tabelle. */
function spaltenSumme(tabellenfeld, spalte) {
var zeilen = ctx.form[tabellenfeld] || [];
var summe = 0;
for (var i = 0; i < zeilen.length; i++) {
if (zeilen[i]) summe += kc.num(zeilen[i][spalte]);
}
return summe;
}
/** Alle Feldnamen und Werte ins Protokoll — zum Nachsehen bei der Fehlersuche. */
function felderProtokollieren() {
var p = ctx.model.primary.elements;
for (var i = 0; i < p.length; i++) {
log.info(' ' + p[i].id + ' = ' + ctx.form[p[i].id]);
}
}
// Anwendung
log.info('Summe: ' + (zahl('menge') * zahl('preis')));
log.info('Menge leer? ' + leer('menge'));
log.info('Positionen: ' + spaltenSumme('positionen', 'preis'));
10. Wo die Grenzen liegen¶
| Das geht nicht | Das nimmst du stattdessen |
|---|---|
über ctx.model Werte speichern |
in ctx.form schreiben |
| neue Zeilen an eine Detail-Tabelle anhängen | Zeilen im Formular anlegen lassen |
JSON.stringify(el.def) ausgeben |
Einträge einzeln lesen: el.def.type |
el.column ungeprüft benutzen |
vorher if (el.column) — Texte und Schaltflächen haben keine Spalte |
ctx.form außerhalb eines Formulars benutzen |
in der Skript-Konsole gibt es die Testfelder $text, $number, $date, $flag |
In der Skript-Konsole ist ctx.model null, weil dort kein Formular geöffnet
ist. Skripte, die in beiden Welten laufen sollen, sichern sich ab:
if (ctx.model) {
log.info('Modell vorhanden: ' + ctx.model.primary.table);
} else {
log.info('Kein Formular-Kontext — Modell nicht verfügbar.');
}
Spickzettel¶
// Wert
ctx.form.feld // lesen / schreiben
$feld // Kurzform
field.feld.value // Feld-Handle
ctx.form['feld'] // Name aus einer Variablen
$['beitrag' + i] // dasselbe als Kurzform (Schleifen!)
field['beitrag' + i].value // und am Handle
'beitrag1' in $ // gibt es das Feld? (ohne Abbruch)
// Umgang mit Werten
field.feld.num // sicher als Zahl
field.feld.numFormat(null,"€",0,2,",",".",3) // als Anzeigetext: 12.345,76 €
$feld.text // als Text, getrimmt, nie null
$feld.bool // als Wahrheitswert
field.feld.text / field.feld.bool // dasselbe am Handle
$feld.isEmpty // nicht ausgefüllt? (ohne Klammern!)
field.feld.isEmpty // dasselbe am Handle
kc.num(ctx.form.feld) // dasselbe ohne Feld-Handle
kc.isEmpty(ctx.form.feld) // (Zwischenwerte, Zeilen, Bedingungen)
// Über das Feld
var el = ctx.model.primary.elements.feld;
el.id el.label el.type el.required
el.def.type // Schema-Sicht
el.column && el.column.ddl // Datenbank-Sicht (kann null sein!)
// Alle Felder
ctx.model.primary.elements.length
ctx.model.primary.elements[i] // auch .betrag / ['betrag']
ctx.model.primary.elements.get(i) // sicher, wenn der Name variabel ist
ctx.model.primary.elements.ids()
// Änderungen
ctx.form._changedField // auslösendes Feld (onFieldChange)
ctx.instanceId === null // noch nie gespeichert
ctx.isNew // neu angelegt (onSubmit / onAfterSave)
// Zeilen
var zeilen = ctx.form.tabellenfeld || [];
zeilen[i].spalte // Wert einer Zelle
Number(event) // angeklickte Zeile (1-basiert)
// Meldungen
field.feld.error('…') / field.feld.hardError('…')
ctx.addError(name, '…')
Weiterlesen¶
ctx— der Skript-Kontext: alles, was nebenctx.formnoch im Kontext steckt.ctx.model— Datenmodell im Skript: die vollständige Referenz zu Elementen, Lookups und Zeilen.- Hooks und Reihenfolge: wann welcher Hook läuft — und damit, welche Werte schon feststehen.
- Detail-Tabellen: wie die Zeilen im Formular entstehen.