Zum Inhalt

JavaScript-Grundlagen

Die Skripte in KCForms sind in JavaScript geschrieben. Diese Seite erklärt die Sprache von Anfang an — Variablen, Text, Zahlen, Datum, Entscheidungen, Schleifen, eigene Funktionen und zum Schluss ein wenig Objektorientierung. Immer mit Beispielen, die du sofort ausprobieren kannst.

Vorwissen ist nicht nötig. Wer zuerst wissen will, was ein Objekt ist, liest JSON und Objekte verstehen — die beiden Seiten ergänzen sich.

Zum Mitmachen

Alle Beispiele laufen in der Skript-Konsole: System-Administration → Werkzeuge & Diagnose → Skript-Konsole. Hineinkopieren, Ausführen klicken, unten die Ausgabe lesen.


Wo dein Skript läuft

Ein Skript in KCForms läuft auf dem Server, nicht im Browser. Das hat zwei Folgen, die man am Anfang oft übersieht:

  • Alles, was man aus Web-Tutorials kennt und was mit der Seite zu tun hat, gibt es hier nicht: kein window, kein document, kein alert(...), kein fetch(...). Meldungen an den Benutzer laufen über view, Aufrufe ins Netz über http.
  • Dafür darf dein Skript Dinge, die ein Browser nie dürfte: Datenbank lesen und schreiben, Mails verschicken, PDFs erzeugen, Dateien anlegen.

Deine erste Zeile ist eine Ausgabe ins Protokoll:

log.info("Hallo KCForms!");     // schreibt eine Zeile ins Skript-Protokoll

Dazu drei Kleinigkeiten, die überall gelten:

  • Kommentare beginnen mit // und werden nicht ausgeführt. Mehrzeilig: /* … */.
  • Jede Anweisung endet mit einem Semikolon ;.
  • Groß- und Kleinschreibung zählt. Log.Info ist nicht log.info.

console.log bringt dir hier nichts

console.log(...) funktioniert zwar, landet aber in der Server-Konsole des Containers — nicht im Skript-Protokoll, das du in der Oberfläche siehst. Nimm immer log.info(...), log.warn(...) oder log.error(...).


Variablen — Dinge mit Namen

Eine Variable ist ein Name für einen Wert. Du vergibst ihn einmal und benutzt ihn danach, so oft du willst:

var vorname = "Anna";
var nachname = "Berg";
var alter = 34;

log.info(vorname + " " + nachname + " ist " + alter);   // Anna Berg ist 34

Es gibt drei Schlüsselwörter dafür. Für den Anfang reicht diese Regel:

Wort Bedeutung Wann
var der klassische Weg überall im Handbuch, funktioniert immer
let wie var, aber nur im umgebenden Block gültig wenn du es magst
const ein Name, der nicht neu gesetzt wird für feste Werte
const MWST_SATZ = 19;     // ändert sich nie
let zwischensumme = 100;  // ändert sich gleich
zwischensumme = 200;

log.info(MWST_SATZ);

Das Handbuch benutzt durchgängig var, damit alle Beispiele gleich aussehen. Beides ist richtig.

Namen sprechen lassen

var b = 3; weiß morgen niemand mehr zu deuten, var bestellmenge = 3; schon. Erlaubt sind Buchstaben, Ziffern und _; beginnen darf ein Name nicht mit einer Ziffer. Üblich ist kleinMitGrossenWortanfaengen.


Die Datentypen

Jeder Wert hat eine Art. Es sind nur wenige, und du kennst die meisten schon aus dem Formular:

Art Fachwort Beispiel
Text String "Anna Berg"
Zahl Number 42, 3.5, -7
Ja/Nein Boolean true, false
Datum Date new Date()
Liste Array ["a", "b"]
Karteikarte Object { name: "Anna" }
leer / gibt's nicht null / undefined —

typeof verrät dir im Zweifel, was du vor dir hast:

log.info(typeof "Anna");     // string
log.info(typeof 42);         // number
log.info(typeof true);       // boolean
log.info(typeof {});         // object

Text (String)

Text steht in Anführungszeichen — doppelte oder einfache, beide gehen. Mit + klebst du Texte aneinander:

var anrede = "Frau";
var name = "Berg";

log.info("Sehr geehrte " + anrede + " " + name);

Kürzer geht es mit Backticks ` und ${…} — der Wert wird direkt in den Text hineingeschrieben:

var anrede = "Frau";
var name = "Berg";

log.info(`Sehr geehrte ${anrede} ${name}`);

Die wichtigsten Handgriffe für Text:

Handgriff Was er tut Ergebnis bei " Anna Berg "
.length Anzahl Zeichen 11
.trim() Leerzeichen vorn und hinten weg "Anna Berg"
.toUpperCase() / .toLowerCase() Groß / klein " ANNA BERG "
.includes("Berg") kommt das vor? true
.indexOf("Berg") ab welchem Zeichen? (-1 = gar nicht) 6
.substring(1, 5) Ausschnitt von–bis "Anna"
.replace("Anna", "Lena") ersetzen (nur das erste Vorkommen) " Lena Berg "
.split(" ") in eine Liste zerlegen ["", "Anna", "Berg", ""]
.padStart(5, "0") vorn auffüllen für Belegnummern
var roh = ctx.form.name;                  // "  anna berg  "
var sauber = roh.trim();                  // "anna berg"

log.info(sauber.toUpperCase());           // ANNA BERG
log.info(sauber.split(" ")[0]);           // anna   ← das erste Wort
log.info(String(7).padStart(4, "0"));     // 0007
log.info("Länge: " + sauber.length);      // Länge: 9

Text und Zahl ineinander umwandeln:

var text = "42";

log.info(Number(text) + 1);      // 43   — Text zu Zahl
log.info(String(42) + "1");      // 421  — Zahl zu Text

Für Werte aus Formularfeldern nimm statt Number(...) besser kc.num(...): Das versteht auch "1.234,56" und liefert bei leerem Feld 0 statt NaN.


Zahlen (Number)

Gerechnet wird mit + - * /, dazu % für den Rest einer Division:

log.info(7 + 3);        // 10
log.info(7 / 2);        // 3.5
log.info("Rest: " + (7 % 2));   // Rest: 1
Handgriff Was er tut
(1.005).toFixed(2) auf 2 Stellen runden, ergibt Text
Math.round(x) kaufmännisch runden
Math.floor(x) / Math.ceil(x) ab- / aufrunden
Math.abs(x) Betrag (Vorzeichen weg)
Math.max(a, b) / Math.min(a, b) größter / kleinster Wert
(1234.5).toLocaleString("de-DE") deutsche Schreibweise: 1.234,5
var summe = kc.num(ctx.form.menge) * kc.num(ctx.form.preis);

log.info(summe.toFixed(2));                        // 37.50
log.info(Math.round(summe * 100) / 100);           // 37.5  — auf Cent gerundet
log.info(summe.toLocaleString("de-DE") + " €");    // 37,5 €

Krumme Kommazahlen

0.1 + 0.2 ergibt nicht 0.3, sondern 0.30000000000000004. So rechnen Computer mit Kommazahlen — das ist kein Fehler von KCForms. Runde deshalb am Ende jeder Rechnung auf Cent: Math.round(x * 100) / 100.


Vergleichen und entscheiden

Ein Vergleich liefert true oder false:

Zeichen Bedeutung
=== gleich (Wert und Art)
!== ungleich
< > <= >= kleiner, größer …
&& und
|| oder
! nicht
var betrag = kc.num(ctx.form.betrag);
var ausland = ctx.form.land !== "DE";

if (betrag > 1000 && ausland) {
  log.info("Freigabe nötig");
} else if (betrag > 1000) {
  log.info("Bitte kurz prüfen");
} else {
  log.info("Alles in Ordnung");
}

Zwei oder drei Gleichheitszeichen?

= setzt einen Wert, === vergleicht. Es gibt auch ==, das großzügig vergleicht und dabei heimlich umwandelt: "0" == 0 ist true, "" == 0 ebenfalls. Nimm immer ===, dann gibt es keine Überraschungen.

Bei vielen festen Werten ist switch übersichtlicher:

var mwst;
switch (ctx.form.land) {
  case "DE": mwst = 19; break;
  case "AT": mwst = 20; break;
  case "CH": mwst = 8.1; break;
  default:   mwst = 0;
}
log.info(mwst + " %");

Für einen einzigen Wert gibt es eine Kurzform — Bedingung ? dann : sonst:

var stufe = kc.num(ctx.form.betrag) > 1000 ? "groß" : "klein";
log.info(stufe);

Was gilt als „leer"?

if (wert) ist false bei "", 0, null, undefined und false. Das ist tückisch: Eine eingetragene Null verhält sich damit wie ein leeres Feld. Für Formularfelder nimm deshalb kc.isEmpty(...) — das ist nur bei „nichts eingegeben" wahr:

if (kc.isEmpty(ctx.form.menge)) {
  log.info("Menge fehlt");
} else {
  log.info("Menge ist ausgefüllt: " + ctx.form.menge);
}

Datum (Date)

new Date() ist der aktuelle Zeitpunkt. KCForms legt zwei Erweiterungen dazu, die es in normalem JavaScript nicht gibt: format(...) und Date.fromString(...).

var jetzt = new Date();
log.info(jetzt.format("dd.MM.yyyy HH:mm"));            // 22.09.2026 14:05

var tag = new Date(2026, 8, 22);                       // Achtung: Monat 8 = September!
log.info(tag.format("yyyy-MM-dd"));                    // 2026-09-22
log.info(tag.format("EEEE, d. MMMM yyyy", "de_DE"));   // Dienstag, 22. September 2026
Muster Bedeutung
dd / MM / yyyy Tag, Monat, Jahr — zweistellig bzw. vierstellig
HH / mm / ss Stunde, Minute, Sekunde
EEEE / MMMM Wochentag, Monatsname ausgeschrieben

Mit Datumswerten lässt sich rechnen. Zieht man zwei voneinander ab, kommen Millisekunden heraus:

var termin = Date.fromString("24.12.2026", "dd.MM.yyyy");
var heute = new Date();
var tage = Math.ceil((termin - heute) / (1000 * 60 * 60 * 24));

log.info(tage + " Tage bis Heiligabend");

So kommt ein Datum ins Formularfeld

Ein Datumsfeld erwartet den Text yyyy-MM-dd. Schreibst du ein Date-Objekt hinein, landet englischer Fließtext im Feld:

log.info(String(new Date(2026, 8, 22)));
// Tue Sep 22 2026 00:00:00 GMT+0000 (GMT)   ← so nicht ins Feld!

ctx.form.datum = new Date(2026, 8, 22).format("yyyy-MM-dd");
log.info(ctx.form.datum);   // 2026-09-22   ← richtig

Listen und Schleifen

Eine Liste hält mehrere Werte in fester Reihenfolge, eine Schleife geht sie der Reihe nach durch:

var artikel = ["Stift", "Block", "Radiergummi"];

for (var i = 0; i < artikel.length; i++) {
  log.info((i + 1) + ". " + artikel[i]);
}

Kürzer mit forEach, und mit for … of liest es sich fast wie ein Satz:

var artikel = ["Stift", "Block", "Radiergummi"];

artikel.forEach(function (name) { log.info(name); });

for (var name of artikel) { log.info(name); }

while wiederholt, solange eine Bedingung gilt — und braucht immer etwas, das sie irgendwann beendet:

var offen = 3;
while (offen > 0) {
  offen = offen - 1;
}
log.info("Rest: " + offen);

Sechs Sekunden, dann ist Schluss

Ein Skript darf höchstens sechs Sekunden laufen; danach bricht KCForms ab. Das schützt den Server vor Endlosschleifen. Große Datenmengen gehören deshalb in einen Zeitplaner-Job, nicht in einen Formular-Hook.

Alles Weitere zu Listen — filtern, umbauen, summieren, sortieren — steht im Kapitel JSON und Objekte verstehen.


Eigene Funktionen

Eine Funktion ist ein Arbeitsschritt mit einem Namen. Du schreibst ihn einmal auf und rufst ihn beliebig oft:

function brutto(netto, steuersatz) {
  return netto * (1 + steuersatz / 100);
}

log.info(brutto(100, 19).toFixed(2));   // 119.00
log.info(brutto(50, 7).toFixed(2));     // 53.50
  • In den Klammern stehen die Parameter — das, was die Funktion braucht.
  • return gibt das Ergebnis zurück und beendet die Funktion sofort.
  • Ohne return liefert sie undefined.

Funktionen sind selbst Werte und dürfen anderen Funktionen übergeben werden — genau das passiert bei forEach, filter oder sort:

var zahlen = [1, 2, 3];
var summe = 0;

zahlen.forEach(function (z) { summe += z; });
log.info(summe);   // 6

Dieselbe Funktion geht kürzer als Pfeilfunktion:

var zahlen = [1, 2, 3];
log.info(zahlen.map(function (z) { return z * 2; }).join("-"));   // 2-4-6
log.info(zahlen.map(z => z * 2).join("-"));                       // dasselbe, kürzer

Was drinnen bleibt, bleibt drinnen

Eine Variable, die innerhalb einer Funktion mit var angelegt wird, gibt es außerhalb nicht (Fachwort: Gültigkeitsbereich, engl. scope). Das ist gewollt: So stören sich zwei Funktionen nicht gegenseitig.


Wenn etwas schiefgeht

Ein Fehler bricht das ganze Skript ab — es sei denn, du fängst ihn auf:

try {
  var zeile = db.queryOne("SELECT * FROM gibt_es_nicht");
  log.info(zeile.name);
} catch (e) {
  log.warn("Das ging schief: " + e.message);
} finally {
  log.info("Dieser Teil läuft immer — auch nach einem Fehler.");
}
  • try — das, was schiefgehen kann.
  • catch (e) — was dann passieren soll; e.message ist der Fehlertext.
  • finally — läuft in jedem Fall, ideal zum Aufräumen.

Du kannst auch selbst abbrechen. Im Formular gehört eine Meldung an den Benutzer aber an das Feld, nicht in einen Fehler:

if (kc.num(ctx.form.menge) > 100) {
  ctx.addError("menge", "Mehr als 100 Stück bitte telefonisch bestellen.");
}

Ein wenig Objektorientierung

Bisher waren Daten und Arbeitsschritte getrennt: hier eine Variable, dort eine Funktion. Objektorientierung heißt schlicht: Beides zusammen in eine Karte packen. Eine Funktion, die in einem Objekt steckt, nennt man Methode, und this meint darin „diese Karte hier":

var kunde = {
  vorname: "Anna",
  nachname: "Berg",
  vollerName: function () {
    return this.vorname + " " + this.nachname;
  }
};

log.info(kunde.vollerName());   // Anna Berg

Brauchst du viele gleichartige Objekte, schreibst du einen Bauplan (eine Klasse) und stellst damit beliebig viele Stück her. new bedeutet „fertige mir ein neues Exemplar nach diesem Bauplan":

class Position {
  constructor(artikel, menge, preis) {
    this.artikel = artikel;
    this.menge = menge;
    this.preis = preis;
  }
  summe() {
    return this.menge * this.preis;
  }
}

var p = new Position("Stift", 3, 1.20);
log.info("Position " + p.artikel + ": " + p.summe().toFixed(2));   // 3.60

Der constructor läuft beim Anlegen und füllt die Karte. Mehr als das brauchst du in KCForms selten — tiefere Themen wie Vererbung kommen im Formular-Alltag praktisch nicht vor.

Du benutzt das längst. Genau so sind die mitgelieferten Werkzeuge gebaut:

var termin = new Date();                  // ein Date-Objekt
var msg = new mail.Message();             // ein Mail-Objekt nach Bauplan
msg.to = "team@example.test";
msg.subject = "Bericht vom " + termin.format("dd.MM.yyyy");

log.info("Vorbereitet für " + msg.to + ": " + msg.subject);

termin.format(...) und msg.to sind nichts anderes als Methode und Eigenschaft eines Objekts — dasselbe Prinzip wie oben.

Willst du eigene Funktionen in mehreren Formularen nutzen, packst du sie in eine Skript-Bibliothek. Dort ist das Muster „ein Objekt mit Funktionen darin" der Normalfall:

var Rechner = {
  MWST: 19,
  brutto: function (netto) {
    return netto * (1 + this.MWST / 100);
  }
};

log.info(Rechner.brutto(100).toFixed(2));   // 119.00

Wie so eine Sammlung systemweit verfügbar wird, steht unter Bibliotheken.


Die Werkzeuge, die KCForms mitbringt

Diese Objekte stehen in jedem Skript bereit — du legst sie nicht selbst an:

Objekt Wofür Erste Zeile
ctx der aktuelle Datensatz und Benutzer ctx.form.name
log ins Protokoll schreiben log.info("...")
kc Zahlen und leere Felder kc.num(ctx.form.betrag)
db Datenbank abfragen und ändern db.query("SELECT ...")
forms Datensätze anderer Formulare forms.list("bestellung")
view Meldungen und Rückfragen view.add(new view.Message("Hallo"))
mail Mails verschicken new mail.Message()
http Schnittstellen aufrufen http.get(url)
pdf, docx, csv, zip, fs Dokumente und Dateien pdf.render(...)

Die vollständige Liste steht im Scripting-Überblick.


Stolpersteine für den Anfang

Stolperstein So ist es richtig
= statt === in einer Bedingung = setzt, === vergleicht
"3" + "4" ergibt "34" Formularwerte mit kc.num(...) in Zahlen wandeln
[10, 9, 100].sort() ergibt 10, 100, 9 sort vergleicht als Text; Zahlen mit sort(function (a, b) { return a - b; })
Ein Date-Objekt im Datumsfeld .format("yyyy-MM-dd") verwenden
console.log zeigt nichts log.info(...) verwenden
if (wert) ist bei einer 0 falsch für Felder kc.isEmpty(...) nehmen
log.info(objekt) zeigt [object Object] log.info(JSON.stringify(objekt)) — mehr dazu
Skript bricht nach 6 Sekunden ab Große Mengen als Job verarbeiten
Betrag ist nicht betrag Groß- und Kleinschreibung zählt überall
var zahlen = [10, 9, 100];

log.info(zahlen.slice().sort().join(","));                              // 10,100,9  ← als Text sortiert
log.info(zahlen.slice().sort(function (a, b) { return a - b; }).join(","));  // 9,10,100

Spickzettel

Ich will … So geht's
etwas ausgeben log.info("Text")
einen Namen vergeben var x = 5;
Texte verbinden "a" + "b" oder `${a} ${b}`
Text zu Zahl kc.num(ctx.form.feld)
auf zwei Stellen runden zahl.toFixed(2)
auf Cent runden Math.round(x * 100) / 100
entscheiden if (…) { … } else { … }
kurz entscheiden bedingung ? dann : sonst
heutiges Datum formatiert new Date().format("dd.MM.yyyy")
Datum in ein Feld schreiben new Date().format("yyyy-MM-dd")
Liste durchgehen liste.forEach(function (x) { … })
eigene Funktion function name(a, b) { return …; }
Fehler abfangen try { … } catch (e) { log.warn(e.message); }
Objekt mit Methode { wert: 1, tuWas: function () { return this.wert; } }
neues Exemplar erzeugen new Date(), new mail.Message()

Weiter