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 PixelnemptyHint— Text, wenn (noch) keine passende Anlage da istfilename— 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— nutzeonSubmit/onAfterSaveoder einen Button-Hook. - Limits:
addrespektiertmaxNumber/maxSize; Überschreiten wirft. - Form-Scope:
ctx.attachmentszeigt ausschließlich die Anlagen dieses Formulardatensatzes. remove/bytesakzeptieren nurids, 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.