JSON und Objekte verstehen¶
Fast alles, was in KCForms zwischen Formular, Datenbank und Skript hin- und herwandert, ist ein Objekt — notiert in einer Schreibweise namens JSON. Das Formular-Schema ist JSON. Die Werte deines Formulars sind ein Objekt. Eine Detail-Tabelle ist eine Liste von Objekten. Eine Antwort aus dem Internet ist meistens JSON.
Wer diese eine Sache versteht, versteht alle diese Stellen auf einmal. Diese Seite erklärt sie von Grund auf — ohne Vorwissen, in kleinen Schritten. Die Sprache selbst — Variablen, Text, Zahlen, Datum, Schleifen, Funktionen — steht nebenan unter JavaScript-Grundlagen.
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 Objekt ist ein Karteikasten¶
Stell dir eine Karteikarte vor. Links steht die Beschriftung, rechts der Inhalt:
| Beschriftung | Inhalt |
|---|---|
| Kunde | Anna Berg |
| Betrag | 249.90 |
| Bezahlt | nein |
Genau das ist ein Objekt. Aufgeschrieben in JSON sieht dieselbe Karte so aus:
Die Bestandteile:
- Geschweifte Klammern
{ }umschließen das ganze Objekt — sie sind der Karteikasten. - Links vom Doppelpunkt steht der Schlüssel (die Beschriftung), rechts der Wert (der Inhalt).
- Zwischen zwei Einträgen steht ein Komma. Nach dem letzten nicht.
Im Skript schreibt man dasselbe ohne die Anführungszeichen um die Schlüssel und holt einen Wert mit einem Punkt heraus:
var bestellung = { kunde: "Anna Berg", betrag: 249.90, bezahlt: false };
log.info(bestellung.kunde); // Anna Berg
log.info(bestellung.betrag); // 249.9
Warum heißt es JSON?
JavaScript Object Notation — „die Schreibweise, in der JavaScript Objekte notiert". JSON ist reiner Text: dieselbe Karteikarte lässt sich damit speichern, per Mail verschicken oder durchs Internet schicken. Ein Objekt im Skript ist der lebendige Zwilling davon.
Was in einem Wert stehen darf¶
Es gibt genau sechs Arten von Werten. Mehr gibt es nicht — alles Weitere entsteht durch Kombination.
| Art | Beispiel | Wofür |
|---|---|---|
| Text | "Anna Berg" |
Namen, Beschreibungen, alles Geschriebene |
| Zahl | 249.90, 7, -3 |
Beträge, Mengen, Zähler |
| Wahrheitswert | true, false |
Ja/Nein, Häkchen im Formular |
| Nichts | null |
„hier steht bewusst nichts" |
| Objekt | { "ort": "Kiel" } |
eine Karte in der Karte |
| Liste | [1, 2, 3] |
mehrere Werte hintereinander |
Ein Punkt trennt die Nachkommastellen, nie ein Komma: 249.90, nicht
249,90. Texte stehen in doppelten Anführungszeichen.
Die letzten beiden Arten machen JSON mächtig — eine Karte darf weitere Karten und Listen enthalten:
{
"kunde": "Anna Berg",
"adresse": { "strasse": "Hafenweg 3", "plz": "24103", "ort": "Kiel" },
"positionen": [
{ "artikel": "Stift", "menge": 3, "preis": 1.20 },
{ "artikel": "Block", "menge": 2, "preis": 2.50 }
]
}
Lies das von außen nach innen: eine Bestellung, darin eine Adresse (ein Objekt) und eine Positionsliste (eine Liste von Objekten). Genau so sieht ein KCForms-Formular mit Detail-Tabelle aus.
Einen Wert herausholen: Punkt oder eckige Klammer¶
Es gibt zwei Schreibweisen, und sie bedeuten dasselbe:
var bestellung = { kunde: "Anna Berg", betrag: 249.90 };
log.info(bestellung.kunde); // Punkt-Schreibweise
log.info(bestellung["kunde"]); // Klammer-Schreibweise — dasselbe Ergebnis
Die Punkt-Schreibweise ist kürzer und im Alltag die erste Wahl. Die eckige Klammer brauchst du in zwei Fällen:
1. Der Schlüssel enthält Sonderzeichen. Ein Punkt funktioniert nur bei
Namen aus Buchstaben, Ziffern und _. Sobald ein Doppelpunkt, Bindestrich oder
Leerzeichen vorkommt, muss die Klammer her — deshalb siehst du im Handbuch
überall die KCForms-Eigenschaften so geschrieben:
var feldDefinition = { "type": "number", "title": "Menge", "kc:column": "menge" };
log.info(feldDefinition.title); // Menge — geht
log.info(feldDefinition["kc:column"]); // menge — nur so, wegen des ":"
2. Der Schlüsselname steht in einer Variablen. Dann weiß das Skript erst zur Laufzeit, welches Feld gemeint ist:
var bestellung = { kunde: "Anna Berg", betrag: 249.90 };
var welchesFeld = "betrag";
log.info(bestellung[welchesFeld]); // 249.9
log.info(bestellung.welchesFeld); // undefined — sucht wörtlich nach "welchesFeld"!
Die zweite Zeile ist ein klassischer Anfängerfehler: Mit dem Punkt sucht das
Skript wörtlich nach einem Schlüssel welchesFeld. Den gibt es nicht.
In die Tiefe: Objekte in Objekten¶
Steckt eine Karte in der Karte, hängst du einfach einen weiteren Punkt an. Lies den Ausdruck von links nach rechts wie eine Wegbeschreibung:
var bestellung = {
kunde: "Anna Berg",
adresse: { strasse: "Hafenweg 3", plz: "24103", ort: "Kiel" }
};
log.info(bestellung.adresse.ort); // Kiel
bestellung→ darin das Fachadresse→ darin das Fachort.
Bei einer Liste kommt eine Zahl in eckigen Klammern dazu — die Position. Gezählt wird ab 0:
var bestellung = {
positionen: [
{ artikel: "Stift", menge: 3 },
{ artikel: "Block", menge: 2 }
]
};
log.info(bestellung.positionen[0].artikel); // Stift (die erste Zeile)
log.info(bestellung.positionen[1].artikel); // Block (die zweite Zeile)
bestellung→ Fachpositionen→ die Zeile Nummer 1 (also die zweite) → darin das Fachartikel.
Gezählt wird ab null
Die erste Zeile ist [0], die zweite [1]. Bei drei Zeilen ist [3] also
schon daneben — das gibt undefined. Die letzte Zeile bekommst du immer mit
liste[liste.length - 1].
Wenn der Schlüssel gar nicht da ist¶
Fragst du nach einem Fach, das es nicht gibt, gibt es keinen Fehler — sondern
undefined, zu Deutsch „gibt's nicht":
Unangenehm wird es erst eine Ebene tiefer. bestellung.adresse ist
undefined, und in einem Nichts kann man nicht weitersuchen:
var bestellung = { kunde: "Anna Berg" };
log.info(bestellung.adresse.ort);
// Fehlermeldung: Cannot read property 'ort' of undefined
Diese Meldung ist die häufigste im ganzen Skripting. Sie sagt immer dasselbe: Der Teil links vom letzten Punkt war nicht da. Drei Wege, das zu vermeiden:
var bestellung = { kunde: "Anna Berg" };
// 1. Ersatzwert mit "||" — nimm das, sonst jenes
var kunde = bestellung.kunde || "unbekannt";
// 2. Erst fragen, dann zugreifen ("&&" bricht ab, sobald etwas fehlt)
if (bestellung.adresse && bestellung.adresse.ort) {
log.info("Ort: " + bestellung.adresse.ort);
} else {
log.info("Ort unbekannt");
}
// 3. Das Fragezeichen: "nur weitergehen, wenn es das gibt"
log.info(bestellung.adresse?.ort); // undefined statt Absturz
Für Listen ist || [] der Standard-Handgriff: „nimm die Liste, und wenn es
keine gibt, eben eine leere". Dann läuft die Schleife danach einfach null Mal,
statt zu scheitern.
var bestellung = {};
var zeilen = bestellung.positionen || [];
log.info(zeilen.length + " Zeilen"); // 0 Zeilen
Werte ändern, ergänzen, entfernen¶
Ein Objekt ist nicht in Stein gemeißelt:
var bestellung = { kunde: "Anna Berg", betrag: 249.90, notiz: "bitte prüfen" };
bestellung.betrag = 199.00; // ändern
bestellung.status = "gepackt"; // ergänzen — den Schlüssel gab es noch nicht
delete bestellung.notiz; // entfernen
log.info(bestellung.status); // gepackt
log.info("notiz" in bestellung); // false — wirklich weg
log.info(JSON.stringify(bestellung));
// {"kunde":"Anna Berg","betrag":199,"status":"gepackt"}
"name" in objekt beantwortet die Frage „gibt es dieses Fach?" mit true oder
false. Das ist genauer als ein Vergleich mit undefined, denn ein Fach darf
auch absichtlich leer sein (null).
Listen¶
Eine Liste (Fachwort: Array) steht in eckigen Klammern und hält beliebig viele Werte in fester Reihenfolge:
var artikel = ["Stift", "Block", "Radiergummi"];
log.info(artikel.length + " Artikel"); // 3 Artikel
log.info(artikel[0]); // Stift
log.info(artikel[artikel.length - 1]); // Radiergummi — der letzte
log.info(artikel.join(", ")); // Stift, Block, Radiergummi
Die wichtigsten Handgriffe:
| Handgriff | Was er tut |
|---|---|
liste.length |
Wie viele Einträge? |
liste[0] |
Der erste Eintrag |
liste.push(x) |
Hängt x hinten an |
liste.join(", ") |
Macht einen Text aus allen Einträgen |
liste.indexOf(x) |
Position von x, oder -1 wenn nicht vorhanden |
liste.slice(0, 5) |
Die ersten fünf — das Original bleibt unverändert |
var gruppen = ["Einkauf", "Buchhaltung"];
gruppen.push("Versand");
if (gruppen.indexOf("Buchhaltung") >= 0) {
log.info("Buchhaltung ist dabei");
}
Schleifen: jeden Eintrag einmal anfassen¶
Eine Schleife wiederholt dieselben Anweisungen für jeden Eintrag einer Liste. Die klassische Form nennt einen Zähler beim Namen:
var artikel = ["Stift", "Block", "Radiergummi"];
for (var i = 0; i < artikel.length; i++) {
log.info((i + 1) + ". " + artikel[i]);
}
// 1. Stift
// 2. Block
// 3. Radiergummi
Die Zeile for (var i = 0; i < artikel.length; i++) liest sich als:
Fang bei 0 an; mach weiter, solange i kleiner als die Länge ist; zähle nach
jedem Durchgang um eins hoch.
Kürzer und ohne Zähler geht es mit forEach — „für jeden Eintrag tu dies":
var artikel = ["Stift", "Block", "Radiergummi"];
artikel.forEach(function (name) {
log.info(name);
});
Beide Formen sind gleichwertig. Ein Unterschied zählt im Alltag:
for |
forEach |
|
|---|---|---|
| Zähler verfügbar | ja (i) |
als zweiter Parameter: function (x, i) |
| vorzeitig abbrechen | ja, mit break |
nein |
| Eintrag überspringen | continue |
return |
var artikel = ["Stift", "Block", "Radiergummi"];
for (var i = 0; i < artikel.length; i++) {
if (artikel[i] === "Block") {
log.info("Gefunden bei " + i);
break; // Schluss, der Rest interessiert nicht mehr
}
}
Über die Schlüssel eines Objekts laufen¶
Ein Objekt hat keine Reihenfolge-Nummern, aber Object.keys macht daraus eine
Liste seiner Schlüssel — und darüber läuft wieder eine gewöhnliche Schleife:
var bestellung = { kunde: "Anna Berg", betrag: 249.90, bezahlt: false };
Object.keys(bestellung).forEach(function (schluessel) {
log.info(schluessel + " = " + bestellung[schluessel]);
});
// kunde = Anna Berg
// betrag = 249.9
// bezahlt = false
Beachte die eckige Klammer: Der Name des Fachs steckt ja in der Variablen
schluessel. Das ist genau Fall 2 von oben.
Nicht bei Datenbankzeilen
Bei einer Zeile aus db.query(...) liefert Object.keys nicht die
Spalten. Dort gilt for (var k in zeile) — warum, steht unter
Zeilen aus der Datenbank.
Die vier Handgriffe für Listen von Objekten¶
Detail-Tabellen, Datenbank-Ergebnisse, Antworten aus dem Internet — sie alle sind Listen von Objekten. Fast alles, was man damit tut, ist eine dieser vier Bewegungen.
Als gemeinsame Grundlage dient diese Positionsliste:
var positionen = [
{ artikel: "Stift", menge: 3, preis: 1.20 },
{ artikel: "Block", menge: 2, preis: 2.50 },
{ artikel: "Radiergummi", menge: 5, preis: 0.80 }
];
1. Zusammenzählen — Schleife mit einem Sammler davor:
var positionen = [
{ artikel: "Stift", menge: 3, preis: 1.20 },
{ artikel: "Block", menge: 2, preis: 2.50 },
{ artikel: "Radiergummi", menge: 5, preis: 0.80 }
];
var gesamt = 0;
positionen.forEach(function (p) {
gesamt += kc.num(p.menge) * kc.num(p.preis);
});
log.info("Gesamt: " + gesamt.toFixed(2)); // Gesamt: 12.60
2. Filtern — filter behält, was die Bedingung erfüllt, und gibt eine
neue Liste zurück:
var positionen = [
{ artikel: "Stift", menge: 3, preis: 1.20 },
{ artikel: "Block", menge: 2, preis: 2.50 },
{ artikel: "Radiergummi", menge: 5, preis: 0.80 }
];
var teure = positionen.filter(function (p) {
return p.preis >= 1.00;
});
log.info(teure.length + " teure Positionen"); // 2 teure Positionen
3. Umbauen — map macht aus jedem Eintrag etwas anderes, ebenfalls als
neue Liste:
var positionen = [
{ artikel: "Stift", menge: 3, preis: 1.20 },
{ artikel: "Block", menge: 2, preis: 2.50 },
{ artikel: "Radiergummi", menge: 5, preis: 0.80 }
];
var namen = positionen.map(function (p) {
return p.artikel;
});
log.info(namen.join(", ")); // Stift, Block, Radiergummi
4. Suchen und sortieren — find liefert den ersten Treffer (oder
undefined), sort ordnet:
var positionen = [
{ artikel: "Stift", menge: 3, preis: 1.20 },
{ artikel: "Block", menge: 2, preis: 2.50 },
{ artikel: "Radiergummi", menge: 5, preis: 0.80 }
];
var block = positionen.find(function (p) { return p.artikel === "Block"; });
log.info("Menge Block: " + (block ? block.menge : "nicht gefunden"));
positionen.sort(function (a, b) { return b.preis - a.preis; }); // absteigend
log.info("Teuerste: " + positionen[0].artikel); // Teuerste: Block
Die Sortier-Funktion bekommt zwei Einträge und antwortet mit einer Zahl:
negativ = a zuerst, positiv = b zuerst. b.preis - a.preis sortiert
deshalb absteigend, a.preis - b.preis aufsteigend.
sort verändert die Original-Liste
filter und map geben eine neue Liste zurück und lassen das Original in
Ruhe. sort und push ändern die Liste selbst. Sortierst du
ctx.form.positionen, steht die Detail-Tabelle danach auch im Formular in
dieser Reihenfolge.
Text und Objekt ineinander umwandeln¶
Ein Objekt lebt im Speicher. Sobald es das Skript verlassen soll — in eine Datei, in ein Textfeld, über das Netz —, muss es Text werden. Und umgekehrt.
| Richtung | Befehl |
|---|---|
| Objekt → Text | JSON.stringify(objekt) |
| Text → Objekt | JSON.parse(text) |
var bestellung = { kunde: "Anna Berg", betrag: 249.90 };
var text = JSON.stringify(bestellung);
log.info(text); // {"kunde":"Anna Berg","betrag":249.9}
log.info(typeof text); // string — ab hier ist es nur noch Text
var wieder = JSON.parse(text);
log.info(wieder.kunde); // Anna Berg — und wieder ein Objekt
Der wichtigste Debug-Handgriff überhaupt
log.info(objekt) schreibt nur [object Object] ins Protokoll — nutzlos.
Willst du sehen, was wirklich drinsteht, verpacke es:
log.info(JSON.stringify(objekt)); // alles in einer Zeile
log.info(JSON.stringify(objekt, null, 2)); // eingerückt, gut lesbar
Die 2 ist die Einrücktiefe. Bei unbekannten Antworten aus dem Internet ist
das der schnellste Weg herauszufinden, wie sie aufgebaut sind.
Eine Ausnahme gibt es: Bei Datenbankzeilen kommt dabei nur {} heraus —
siehe Zeilen aus der Datenbank.
JSON.parse erwartet gültigen JSON-Text und wirft sonst einen Fehler. Bei
Daten aus fremder Quelle deshalb absichern:
var antwort = "{ kaputt ohne Anführungszeichen }";
try {
var daten = JSON.parse(antwort);
log.info(daten.kunde);
} catch (e) {
log.warn("Konnte die Antwort nicht lesen: " + e.message);
}
Sonderfall: Zeilen aus der Datenbank¶
Eine Zeile aus db.queryOne(...) oder db.query(...) sieht aus wie ein
Objekt und verhält sich im Alltag auch so — sie ist aber in Wahrheit ein
Java-Objekt, das KCForms ins Skript durchreicht. Zwei Dinge funktionieren
deshalb anders:
var zeile = db.queryOne("SELECT 'Anna' AS vorname, 42 AS punkte");
log.info(zeile.vorname); // Anna — normaler Zugriff: geht
log.info(JSON.stringify(zeile)); // {} ← leer, obwohl Daten drin sind!
var spalten = [];
for (var k in zeile) { spalten.push(k); }
log.info(spalten.join(", ")); // vorname, punkte ← so bekommst du die Spalten
| bei einer Datenbankzeile | |
|---|---|
zeile.spalte |
funktioniert wie gewohnt |
JSON.stringify(zeile) |
gibt {} — zum Anschauen unbrauchbar |
Object.keys(zeile) |
liefert nicht die Spalten, sondern interne Java-Methoden |
for (var k in zeile) |
liefert die Spaltennamen — nimm diese Form |
Brauchst du eine Zeile als echtes Objekt (zum Ausgeben, Weiterschicken oder Umbauen), schreibe sie einmal um:
function alsObjekt(zeile) {
var o = {};
for (var k in zeile) { o[k] = zeile[k]; }
return o;
}
var zeile = db.queryOne("SELECT 'Anna' AS vorname, 42 AS punkte");
log.info(JSON.stringify(alsObjekt(zeile))); // {"vorname":"Anna","punkte":42}
Die Liste aus db.query(...) ist dagegen eine ganz normale Liste:
length, forEach, filter und map funktionieren wie oben beschrieben —
nur die einzelnen Zeilen darin sind eben Datenbankzeilen.
Sieben Stolpersteine¶
1. Zahlen aus dem Formular sind Text. Der Browser schickt jedes Eingabefeld
als Zeichenkette — auch ein Zahlenfeld. + klebt Texte zusammen, statt zu
rechnen:
log.info("falsch: " + (ctx.form.menge + ctx.form.preis)); // 34
log.info("richtig: " + (kc.num(ctx.form.menge) + kc.num(ctx.form.preis))); // 7
Deshalb gehört um jeden Formularwert, mit dem gerechnet wird, ein
kc.num(...). Es versteht auch "1.234,56".
2. Ein Objekt ist kein Text. Siehe oben: JSON.stringify beim Ausgeben.
3. Zwei Namen, ein Objekt. Zuweisen kopiert nicht — beide Namen zeigen auf dieselbe Karte:
var a = { betrag: 249.90 };
var b = a; // KEINE Kopie — b ist derselbe Karteikasten
b.betrag = 1;
log.info("a: " + a.betrag); // a: 1 ← überrascht!
var c = JSON.parse(JSON.stringify(a)); // echte Kopie, auch in der Tiefe
c.betrag = 249.90;
log.info("Kopie: " + c.betrag + ", Original: " + a.betrag);
4. JSON-Text ist streng. Anders als das Skript verzeiht JSON nichts:
{
"kunde": "Anna Berg", ← doppelte Anführungszeichen, keine einfachen
"betrag": 249.90, ← Punkt als Dezimaltrenner
"bezahlt": false ← KEIN Komma nach dem letzten Eintrag
}
Kommentare sind in JSON ebenfalls nicht erlaubt. Bricht der JSON-Editor im Formular-Editor mit einer Fehlermeldung ab, ist es fast immer eines dieser drei Dinge — meist ein Komma zu viel.
5. null, undefined und "" sind drei verschiedene Dinge. „Fach
absichtlich leer", „Fach gibt es nicht" und „leerer Text". In der Formular-Welt
willst du meist alle drei gleich behandeln — dafür gibt es
kc.isEmpty(...).
6. === statt == vergleichen. == vergleicht großzügig und macht aus
Text heimlich Zahlen: "0" == 0 ist true, "" == 0 auch. === vergleicht
Wert und Art und liefert keine Überraschungen.
7. Groß- und Kleinschreibung zählt. bestellung.Kunde ist nicht
bestellung.kunde. Der falsch geschriebene Zugriff meldet keinen Fehler,
sondern liefert still undefined — deshalb lohnt beim Suchen ein
log.info(JSON.stringify(objekt)), um die echten Schlüssel zu sehen.
Wo dir JSON in KCForms begegnet¶
| Stelle | Was es ist | Mehr dazu |
|---|---|---|
| Formular-Schema | ein großes Objekt mit allen Feldern und kc:-Eigenschaften |
Grundlagen |
ctx.form |
ein Objekt mit allen Feldwerten des Datensatzes | ctx |
| Detail-Tabellen | eine Liste von Objekten unter ctx.form.<name> |
Detail-Tabellen |
db.query(...) |
eine Liste von Objekten — ein Objekt je Ergebniszeile | db |
forms.list(...) |
ebenso: eine Liste von Datensatz-Objekten | forms |
http.get(...) |
Antworten aus dem Internet, meist JSON-Text | HTTP |
| REST-Services | dein Service bekommt und liefert JSON | REST-Services |
ctx.model |
die Beschreibung deines Datenmodells als Objekt | ctx.model |
| Properties, Transport, Export | Konfigurationen und Formulare als JSON-Dateien | Properties |
Ein Beispiel, das drei dieser Stellen verbindet — Detail-Tabelle lesen, umbauen, als Text in ein Feld schreiben:
var zeilen = ctx.form.positionen || [];
var summe = 0;
zeilen.forEach(function (z) {
summe += kc.num(z.menge) * kc.num(z.preis);
});
ctx.form.betrag = summe;
ctx.form.ergebnis = zeilen.map(function (z) {
return z.artikel + " (" + z.menge + ")";
}).join(", ");
log.info(ctx.form.ergebnis); // Stift (3), Block (2)
log.info("Summe: " + summe.toFixed(2));
Spickzettel¶
| Ich will … | So geht's |
|---|---|
| einen Wert lesen | objekt.feld oder objekt["feld"] |
| einen Wert tief drinnen lesen | objekt.a.b[0].c |
| absichern, dass nichts fehlt | objekt.a?.b oder objekt.a && objekt.a.b |
| einen Ersatzwert setzen | objekt.feld || "Standard" |
| einen Wert setzen oder ergänzen | objekt.feld = wert |
| ein Fach entfernen | delete objekt.feld |
| prüfen, ob ein Fach existiert | "feld" in objekt |
| alle Schlüssel durchgehen | Object.keys(objekt).forEach(...) |
| Länge einer Liste | liste.length |
| etwas anhängen | liste.push(wert) |
| jede Zeile durchgehen | liste.forEach(function (z) { … }) |
| zusammenzählen | Sammler + forEach |
| aussortieren | liste.filter(function (z) { return … }) |
| umbauen | liste.map(function (z) { return … }) |
| den ersten Treffer suchen | liste.find(function (z) { return … }) |
| sortieren | liste.sort(function (a, b) { return a.x - b.x }) |
| ein Objekt anschauen | log.info(JSON.stringify(objekt, null, 2)) |
| Objekt → Text | JSON.stringify(objekt) |
| Text → Objekt | JSON.parse(text) |
| echte Kopie | JSON.parse(JSON.stringify(objekt)) |
Weiter¶
- JavaScript-Grundlagen — die Sprache selbst, von Variablen bis Objektorientierung
- XML verstehen und verarbeiten — das Gegenstück für XML-Daten
- Scripting-Überblick — was ein Skript in KCForms überhaupt tut
- Erste Schritte in Beispielen — 22 kleine Skripte zum Ausprobieren
ctx— das Objekt, das jedes Skript bekommtkc,log, Datum —kc.num,kc.isEmptyund die$feld-Kurzform- Detail-Tabellen — Listen von Objekten im Formular