Zum Inhalt

Dateianlagen (ctx.attachments)

Jeder Formulardatensatz kann Dateianlagen tragen (hochgeladene Belege, generierte CSV-/PDF-Exporte …). Im Skripting stehen sie unter ctx.attachments bereit — zum Lesen (zählen, Metadaten) und zum Schreiben (hinzufügen, entfernen, Bytes lesen).

Voraussetzung: Anlagen aktivieren

Anlagen müssen je Formular eingeschaltet sein — sonst gibt es keinen Reiter Dateianlagen, und ctx.attachments.add(...) wirft. Der Schalter Dateianlagen aktivieren steht in den Formular-Eigenschaften unter Erweiterte Einstellungen, zusammen mit Höchstzahl und -größe; siehe Metadaten. Ohne eigene Werte gelten die globalen Grenzen.


Lesen

ctx.attachments verhält sich zugleich wie ein Array und wie ein Objekt:

Zugriff Ergebnis Bedeutung
ctx.attachments.length Zahl Anzahl der Anlagen dieses Datensatzes
ctx.attachments[i] Objekt | null i-te Anlage (0-basiert)
ctx.attachments.get(i) Objekt | null wie [i]
ctx.attachments.maxNumber Zahl effektive Höchstzahl für dieses Formular
ctx.attachments.maxSize Zahl effektive Maximalgröße pro Datei (MB)

Jede Anlage ist ein Objekt mit diesen Feldern:

Feld Typ Beschreibung
id Zahl eindeutige Anlagen-ID (für remove/bytes, Download-Links)
filename Text Dateiname
contentType Text MIME-Typ, z. B. application/pdf, image/jpeg, text/csv
sizeBytes Zahl Dateigröße in Bytes
uploadedBy Text Loginname des Hochladenden
uploadedAt Text Zeitstempel

Der Datei-Inhalt steckt bewusst nicht in diesem Objekt (kein .bytes/.data) — das verhindert versehentliche Riesen-Kopien in den JS-Heap. Die Bytes holt man gezielt über ctx.attachments.bytes(id).

Beispiel: zählen und auflisten

// onLoad — Anzahl in ein (read-only) Feld schreiben und Namen loggen
ctx.form.anzahl_anlagen = ctx.attachments.length;

for (var i = 0; i < ctx.attachments.length; i++) {
  var a = ctx.attachments[i];
  log.info((i + 1) + ". " + a.filename + " (" + a.contentType + ", " + a.sizeBytes + " Byte)");
}

if (ctx.attachments.length >= ctx.attachments.maxNumber) {
  log.warn("Anlagen-Limit erreicht (" + ctx.attachments.maxNumber + ").");
}

Beispiel: nach Typ/Name filtern

// Anzahl der PDF-Anlagen und Gesamtgröße
var pdfCount = 0, totalBytes = 0;
for (var i = 0; i < ctx.attachments.length; i++) {
  var a = ctx.attachments[i];
  totalBytes += a.sizeBytes;
  if (a.contentType === "application/pdf") pdfCount++;
}
log.info(pdfCount + " PDF(s), gesamt " + Math.round(totalBytes / 1024) + " kB");

Schreiben

Nur nach dem Speichern

add / remove / bytes brauchen einen gespeicherten Datensatz (eine Instanz-ID). Sie funktionieren in onSubmit, onAfterSave und in Custom-Hooks (kc:button.scriptHook) auf bestehenden Datensätzen. In onLoad vor dem ersten Speichern gibt es noch keine Instanz — ein Aufruf wirft dann eine sprechende Ausnahme.

add(name, quelle [, contentType]) → neue Anlage

Fügt dem Datensatz eine Datei hinzu und gibt die neue Anlage (Objekt mit id, filename, …) zurück. quelle darf sein:

Quelle Beispiel
Text (String → UTF-8) ctx.attachments.add("notiz.txt", "Hallo Welt")
byte[] ctx.attachments.add("bild.png", meineBytes, "image/png")
CSV-Printer (ByteSource) ctx.attachments.add("export.csv", printer)
fs-Datei ctx.attachments.add("report.pdf", fs["/generated/report.pdf"])
Zahlen-Array ctx.attachments.add("daten.bin", [72,73])

Fehlt contentType, wird er aus der Dateiendung geraten (.pdf, .csv, .txt, .xml, .json, .png, .jpg, .zip, .xlsx, .docx …). Die Limits (maxNumber, maxSize) werden durchgesetzt.

remove(id) → true/false

Entfernt gezielt eine Anlage dieses Datensatzes anhand ihrer id. Gehört die id nicht zu diesem Datensatz, passiert nichts (false).

bytes(id) → byte[]

Lädt den Inhalt einer Anlage dieses Datensatzes. Nützlich, um eine hochgeladene Datei im Skript weiterzuverarbeiten (parsen, umwandeln, prüfen).


Beispiele

CSV generieren und anhängen

Der Klassiker: eine Übersicht als CSV erzeugen und dem Datensatz beilegen.

// Custom-Hook "uebersichtAnhaengen" (Button)
var csv = modules.use("CSVService");          // oder: var p = new csv.StandardPrinter()
var p   = new csv.StandardPrinter({ separator: ";" });

p.println(["Nr", "Dateiname", "Typ", "Groesse (Byte)"]);
for (var i = 0; i < ctx.attachments.length; i++) {
  var a = ctx.attachments[i];
  p.println([i + 1, a.filename, a.contentType, a.sizeBytes]);
}

var neu = ctx.attachments.add("anlagen-uebersicht.csv", p);
log.info("angehaengt: " + neu.filename + " (#" + neu.id + ", " + neu.sizeBytes + " Byte)");

Der StandardPrinter ist eine ByteSource — er lässt sich direkt an add() übergeben (ebenso an delivery.deliver(p) oder fs["/x.csv"] = p).

Aus dem Dateisystem hinzufügen

// Eine im fs-Sandkasten erzeugte/abgelegte Datei anhängen
var file = fs["/vorlagen/merkblatt.pdf"];
if (file.exists) {
  ctx.attachments.add("merkblatt.pdf", file);
}

Text-Datei anhängen

var protokoll = "Bearbeitet von " + ctx.user.name + " am " + new Date().format("dd.MM.yyyy") + "\n";
ctx.attachments.add("protokoll.txt", protokoll);

Alle Anlagen an eine E-Mail hängen

// Custom-Hook "allePerMail" (Button)
if (ctx.attachments.length === 0) {
  new view.Message("Keine Anlagen vorhanden.");
} else {
  var msg = new mail.Message();
  msg.to      = ctx.user.email;
  msg.subject = "Ihre " + ctx.attachments.length + " Dateianlagen";

  var mp = new mail.MessageMultipart();
  mp.add(new mail.MessagePart("Anbei alle Dateianlagen dieses Vorgangs."));
  mp.addAttachments(ctx.attachments, "anlage-${index}");   // alle auf einmal

  msg.content = mp;
  mail.send(msg);
  new view.Message(ctx.attachments.length + " Anlagen versendet.");
}

Einzeln geht auch: mp.addAttachment(ctx.attachments[0], "Rechnung.pdf", "application/pdf"). Zusätzlich kann eine Mail das Formular selbst als PDF (mp.addOutput("Beleg.pdf")) oder als XML (mp.addData("Beleg.xml")) mitschicken. Details: mail.

Gezielt entfernen

// die zuletzt hinzugefügte Anlage entfernen
if (ctx.attachments.length > 0) {
  var last = ctx.attachments[ctx.attachments.length - 1];
  ctx.attachments.remove(last.id);
}

// oder: alle CSV-Anlagen entfernen
for (var i = ctx.attachments.length - 1; i >= 0; i--) {   // rückwärts, weil sich die Liste ändert
  if (ctx.attachments[i].contentType === "text/csv") {
    ctx.attachments.remove(ctx.attachments[i].id);
  }
}

Inhalt lesen und weiterverarbeiten

// Erste CSV-Anlage einlesen und Zeilen zählen
for (var i = 0; i < ctx.attachments.length; i++) {
  var a = ctx.attachments[i];
  if (a.contentType === "text/csv") {
    var bytes = ctx.attachments.bytes(a.id);      // byte[]
    var text  = String.fromCharCode.apply(null, bytes);
    ctx.form.zeilen = text.split("\n").length;
    break;
  }
}

Anlage im Dateisystem sichern

// Eine Anlage in den fs-Sandkasten schreiben (z.B. für einen Batch-Export)
var a = ctx.attachments[0];
fs["/export/" + a.filename].writeAttachment(a);   // Kurzform, lädt die Bytes selbst

PDF-Anlagen im Formular anzeigen

Eine PDF-Anlage lässt sich direkt im Formular (Web und PDF-Ausdruck) einbetten — mit dem kc:pdfViewer-Element:

"beleg_pdf": {
  "type": "string",
  "kc:colspan": "full",
  "kc:pdfViewer": {
    "height": 520,
    "emptyHint": "Lade ein PDF hoch — es erscheint hier.",
    "filename": "rechnung"
  }
}
  • height — Höhe in Pixeln
  • emptyHint — Text, wenn (noch) keine passende Anlage da ist
  • filename — optionaler Namensfilter (zeigt die Anlage, deren Name das enthält)

Sichtbarkeit an Anlagen koppeln geht auch clientseitig: "kc:visibleIf": "ctx.attachments.length > 0". Ausführlich: PDF-Viewer.


Referenz — alle Attribute & Methoden

ctx.attachments

Zugriff Ergebnis Bedeutung
.length Zahl Anzahl der Anlagen
[i] / .get(i) Objekt | null i-te Anlage
.maxNumber Zahl Höchstzahl (Formular)
.maxSize Zahl Höchstgröße je Datei in MB
.add(name, quelle [, contentType]) Objekt Anlage hinzufügen
.remove(id) boolean Anlage entfernen
.bytes(id) byte[] Inhalt einer Anlage lesen

Anlage-Objekt — { id, filename, contentType, sizeBytes, uploadedBy, uploadedAt }

Wo verfügbar

Operation onLoad (neu) onLoad (gespeichert) onSubmit / onAfterSave / Custom-Hook
Lesen (length, [i], …) ✓ (leer) ✓ ✓
add / remove / bytes ✗ (wirft) ✓ ✓

Grenzen & Fehler

  • Nur nach dem Speichern: ohne Instanz-ID werfen add/remove/bytes — nutze onSubmit/onAfterSave oder einen Button-Hook.
  • Limits: add respektiert maxNumber/maxSize; Überschreiten wirft.
  • Form-Scope: ctx.attachments zeigt ausschließlich die Anlagen dieses Formulardatensatzes.
  • remove/bytes akzeptieren nur ids, die zu diesem Datensatz gehören.

Ein kleines Beispiel liefert die Reisekostenabrechnung aus den Beispielen: Sie definiert die Regel @HatBelege mit dem Ausdruck ctx.attachments.length > 0.