Zum Inhalt

forms — Datensätze per Skript anlegen, lesen, ändern, löschen

Das forms-Binding gibt Skripten programmatischen Zugriff auf Formular-Datensätze: anlegen, lesen, aktualisieren, löschen und iterieren. Typischer Einsatz: eingehende Daten aus einem REST-Service, einem WebService oder dem Dateisystem in ein Formular schreiben — oder in einem Job Datensätze reihenweise verarbeiten.

forms ist in allen Skript-Kontexten verfügbar (Form-Hooks, REST-Services, Scheduler-Jobs, Skript-Konsole).

Roh, aber konsistent

forms.create/update nutzen denselben Persistenz-Kern wie ein UI-Submit (Spalten-Zuordnung, Typ-Coercion, kc:maxLength, form_version-Stempel, Detail-Tabellen). Sie führen aber keine Formular-Validierung und keine Lifecycle-Hooks (onValidate/onBeforeSave/onSubmit/…) aus. Das ist Absicht: schnell und vorhersehbar für Import/Automatisierung — und ohne Rekursionsgefahr, falls forms.* selbst aus einem Hook läuft. Wer Prüfungen braucht, baut sie vor dem create/update selbst ein.

Überblick

Aufruf Ergebnis
forms.create(formId, values [, opts]) Legt einen Datensatz an → neue id (Zahl)
forms.get(formId, id [, opts]) Datensatz als Objekt {id, …Felder…, …Detail-Listen…} oder null
forms.list(formId [, opts]) Array [{id, …Felder…}, …] (ohne Detail-Listen)
forms.forEach(formId, [opts,] callback) Ruft callback(record) je Datensatz
forms.update(formId, id, values [, opts]) Teil-Update (nur übergebene Felder) → betroffene Zeilen (0/1)
forms.delete(formId, id [, opts]) Löscht + räumt Anhänge/Änderungsprotokoll auf → betroffene Zeilen
forms.exists(formId, id) true / false
forms.validate(formId, values) Nur prüfen (nicht schreiben) → Array [{field, message}] (leer = gültig)

Anlegen — auch mit Detail-Tabellen

// z.B. im REST-Handler: eingehende Bestellung in ein Formular schreiben
var id = forms.create('bestellung', {
  kunde:      body.kunde,
  betrag:     body.betrag,
  positionen: body.positionen   // kc:childTable-Feld → Detail-Zeilen (Array von Objekten)
});
return { ok: true, id: id };

Detail-Zeilen (kc:childTable-Felder) werden automatisch mitgeschrieben, wenn der Wert eine Liste von Objekten ist — im selben verschachtelten Format wie ctx.form.

Prüfen vor dem Import — Problemdatensätze abweisen

Damit fehlerhafte Eingangsdaten nicht importiert werden, sondern die Fehlermeldung an den aufrufenden Service zurückgeht:

// REST-Handler: erst prüfen, dann anlegen
var errs = forms.validate('bestellung', body);   // [] = gültig
if (errs.length) {
  return { ok: false, status: 422, errors: errs };   // an den Service zurückmelden
}
var id = forms.create('bestellung', body);
return { ok: true, id: id };

forms.validate(formId, values) liefert ein Array von {field, message[, severity]} (leer = gültig). Alternativ bricht opts.validate: true ein create/update bei Fehlern ab und wirft eine sprechende Meldung — praktisch mit try/catch:

try {
  forms.create('bestellung', body, { validate: true });
} catch (e) {
  return { ok: false, error: String(e.message) };   // "forms: Validierung fehlgeschlagen — …"
}

In beiden Fällen wird bei Fehlern nichts geschrieben.

Was geprüft wird

validate prüft auf Schema-Ebene: Pflichtfelder, Typ (Zahl/Datum), kc:maxLength, Auswahl (enum) und Validierungsmuster. Bei update wird gegen den zusammengeführten Datensatz geprüft (bereits gespeicherte Pflichtfelder gelten nicht als leer). Formular-Hooks (onValidate) und kc:validator-Feldvalidatoren laufen dabei nicht — solche Geschäftsregeln baut der Import-Skriptautor bei Bedarf selbst ein.

Lesen

var rec = forms.get('bestellung', 42);
// rec = { id: 42, kunde: 'ACME', betrag: 900, positionen: [ {…}, {…} ] }  |  null

get liefert ein serialisierbares Objekt (JSON.stringify, Rückgabe aus REST-Handlern und Feldzugriff funktionieren) inklusive der Detail-Listen. list lässt Detail-Listen aus Performance-Gründen weg — für sie get(id) je Satz.

// Alle Datensätze durchgehen
forms.forEach('bestellung', function (rec) {
  if (rec.betrag > 1000) log.info('Groß: #' + rec.id);
});

Aktualisieren (Teil-Update)

forms.update('bestellung', 42, { status: 'freigegeben' });

Es werden nur die übergebenen Felder geschrieben — nicht genannte Spalten bleiben unangetastet. Enthält values Detail-Listen, werden diese ersetzt.

Löschen

forms.delete('bestellung', 42);

Räumt zusätzlich Dateianlagen und das Änderungsprotokoll dieses Satzes weg; Detail-Zeilen verschwinden über die Fremdschlüssel-Kaskade. Keine Datenbankleichen.

Optionen (opts)

Letztes Argument von create/get/list/update/delete:

Option Default Wirkung
owner ctx.user Eigentümer (setzt createdBy und idUser)
createdBy / idUser aus ctx.user Eigentümer-Felder einzeln setzen
scope 'all' 'all' = alle Datensätze sichtbar (System-Bypass); 'mine' = nur eigene (id_user-Filter) bei get/list/update/delete
children true Detail-Listen mitschreiben (create/update)
validate false vor create/update auf Schema-Ebene prüfen; bricht bei Fehlern ab
// Import ohne konkreten Benutzer, als System-Eigentümer:
forms.create('bestellung', values, { owner: 'import-job' });

// nur eigene Datensätze löschen:
forms.delete('bestellung', 42, { scope: 'mine' });

Standardmäßig gehört ein neuer Datensatz ctx.user (in Jobs "scheduler", in REST dem Remote-User), und get/update/delete/list sehen alle Datensätze (System-Bypass). scope:'mine' schränkt auf die eigenen ein.

Grenzen

  • Schema-Validierung ja, Hooks nein. forms.validate / opts.validate prüfen auf Schema-Ebene (s.o.). Die vollen Lifecycle-Hooks (opts.runHooks — onValidate/onBeforeSave/…) sind noch nicht umgesetzt (Rekursionsgefahr) und werfen einen Fehler, wenn gesetzt.
  • Memory-Formulare (ohne Tabelle) werden abgelehnt.
  • Detail-Tabellen: der kc:childTable-Pfad wird unterstützt.
  • Zahlenfelder kommen als Zahl zurück; DECIMAL-Felder je nach Dezimalstellen als Zahl (identisch zur übrigen KCForms-Dekodierung).

Schulungs-Beispiel (zwei Formulare)

Für Schulungen gibt es ein Beispielpaar „Schulung: forms-API" — installierbar unter System-Administration → Formular-Bausteine → Beispiele, danach im Katalog „Demo", Ordner „Schulung":

  • „Aufträge (Datenquelle)" (schulung-auftraege) — ein normales Erfassungsformular (Auftragsnummer + Kunde sind Pflicht). Das ist das Ziel der Operationen; man befüllt es von Hand oder per Skript.
  • „Skript-Werkbank (forms-API)" (schulung-werkbank) — je ein Button pro Operation. Jeder Button löst einen Server-Hook aus, der die zugehörige forms.*-Funktion gegen die Datenquelle ausführt und das Ergebnis in einem Dialog (view.add) zeigt; ein Kurztext landet zusätzlich im Feld „Ergebnis".

Trainer-Ablauf: Felder ausfüllen → Anlegen (create), die neue id landet in „Datensatz-id" → Alle laden (list), Öffnen (get), Status setzen (update), Löschen (delete). Für die Validierung: Pflichtfeld leer lassen → Anlegen mit Prüfung / Nur prüfen zeigt den Fehler, ohne zu speichern. Die Hook-Skripte (demoCreate, demoValidate, … im Reiter „Skripte" der Werkbank) sind ausführlich kommentiert und dienen als Vorlage.

So zeigt ein Button-Hook sein Ergebnis

Ein kc:button mit scriptHook: "meinHook" ruft den Hook meinHook auf dem Server auf. Sein Ergebnis wird immer in die Maske zurückgespiegelt (kein kc:reflectServerValues nötig) — kurze Werte also einfach per ctx.form.<feld> = … setzen. Für mehrzeilige/JSON-Ausgabe einen Dialog: view.add(new view.Message('html:<pre>' + JSON.stringify(r, null, 2) + '</pre>', 'Titel', { modal:true, okButton:true })). Der Hook muss im Reiter Skripte des Formulars angelegt sein — andere Namen lehnt der Server ab.

Abgrenzung zu db

Das db-Binding schreibt rohes SQL gegen die per-Form-Tabelle — ohne Spalten-/Typ-Auflösung, ohne Detail-Verteilung, ohne form_version und mit hartcodiertem Tabellennamen. forms.* nimmt dir all das ab und ist der empfohlene Weg für Datensatz-CRUD. db bleibt für freie Abfragen/Joins.