Developer Kit · Patterns
Data Bag Actions
Data Bag Actions (partielle Updates in JSON-Töpfen)
AI Defaults
- Gilt für alle Widgets mit `actions`
- , die den Ninox Action Performer nutzen (Button, Layout, Table, KPI Bar, Calendar, Drawing, …).
- Syntax:
- `dataBag: { encoding, base, patch }` — `patch` folgt JSON Merge Patch (RFC 7386): Key setzen/überschreiben; `null` / `''` löscht den Key. -** `encoding`:** `"raw"` (Default) — Plain-JSON im Textfeld via `JSON.stringify` / `parseJSON`.** `encoding` weglassen**, wenn nicht Legacy-Daten. -** `base`:** aktueller Feldwert (Rohstring aus dem Textfeld oder bereits geparstes Objekt).
- Lesen in Formeln:
- `parseJSON(text(helper_uiState))` bzw. `parseJSON(helper_data)` — **ohne** `urlDecode`.
- Alternative:
- plain `value` auf der Action, wenn das Zielfeld nur einen einzelnen Wert hält.
- Legacy:
- `"urlEncoded"` nur bei bestehenden url-kodierten Feldern (`parseJSON(urlDecode(...))`). Siehe Abschnitt unten.
- Nicht verwechseln:
- Widget-Prop `dataBag: { key, encoding }` in **arcCustomDrawing** — nur für Shape-Geometrie in verschachtelten JSON-Töpfen. Siehe
custom-drawing. ---
Problem
Viele UI-Zustände (Tab, Filter, ausgewählte ID) liegen in einem JSON-Textfeld (State-Bag). Beim Klick soll nur ein Key geändert werden — ohne den Rest zu löschen.
Ohne dataBag musst du in Ninox den Topf manuell lesen, setItem aufrufen und wieder serialisieren (siehe <a href="/docs/ninox/templates/tips_statefield">State-Bag in einem Textfeld</a>).
Mit dataBag auf der Action übernimmt der Action Performer Read → Patch → Write.
Action-Syntax
Partiell patchen (empfohlen für State-Bags)
{
type: "update",
recordId: Nr,
fieldId: fieldId(Nr, "helper_mainContentState"),
dataBag: {
base: helper_mainContentState,
patch: { selectedShape: "123", activeTab: "details" }
}
}
- base: Rohwert aus dem Feld (String oder bereits geparstes Objekt — der Helfer normalisiert). - patch: nur die Keys, die sich ändern sollen. - Key löschen: patch: { statusFilter: null } oder "". - encoding: optional; Default "raw" — Plain-JSON im Textfeld.
Einfacher Wert (dediziertes Feld)
{
type: "update",
recordId: Nr,
fieldId: fieldId(Nr, "helper_selectedId"),
value: "123"
}
Wo funktioniert das?
| Kontext | dataBag auf Action | Widget-dataBag (key) |
|---|---|---|
| arcCustomButton, Table, KPI Bar, Calendar, Input, … | ja (update + create) | — |
arcCustomCalendarWeek — createNew | ja (create, inkl. ##newRecordId##) | — |
arcCustomLayout — Block-actions (Klick) | ja (update + create) | — |
arcCustomLayout — Widget-actions (Klick auf gesamtes Layout) | ja (update + create) | — |
arcCustomDrawing — Shape-actions (Klick) | ja (update) | — |
| arcCustomDrawing — Geometrie speichern | — | ja (key + optional encoding) |
Alle Widgets teilen sich dieselbe Action-Engine für update und create (inklusive dataBag).
> Hinweis für arcCustomDrawing Create: Das Drawing-Widget legt neue Shapes über einen eigenen Create-Pfad an, nicht über die üblichen actions. Dort gilt weiterhin setNewRecordId. dataBag auf type: "create" gilt nur für explizit definierte Create-Actions in anderen Widgets (z. B. Button, Layout).
React / Vue / WeWeb: Actions werden an den Host durchgereicht (onAction / emit('action')). dataBag-Patching ist dort nicht automatisch — der Host muss den Patch selbst anwenden oder den Klartext-value nutzen.
dataBag auf create-Actions
Neu: dataBag funktioniert auch auf type: "create". Nach dem Anlegen des Datensatzes wird der Platzhalter ##newRecordId## durch die numerische ID des neuen Records ersetzt — dann wird der Bag gepatch und ins Zielfeld geschrieben.
Platzhalter ##newRecordId##
Ersetzt in beliebiger Tiefe (Strings, Objekte, Arrays) die neue numerische Record-ID nach dem Create.
{
type: "create",
tableId: tableId("Aufgaben"),
changeFieldValues: [{
fieldId: fieldId("Aufgaben", "Projekt"),
value: Nr
}],
dataBag: {
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
base: helper_uiState,
patch: { selectedTaskId: "##newRecordId##" }
}
}
Ablauf: Create → neue ID → ##newRecordId## auflösen → readBag(base) → applyPatch(bag, patch) → writeBag → database.update
Mehrere Bags nach Create (Array)
dataBag: [
{
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
base: helper_uiState,
patch: { selectedTaskId: "##newRecordId##", activeTab: "details" }
},
{
recordId: parentNr,
fieldId: fieldId(parentNr, "helper_dashState"),
base: helper_dashState,
patch: { lastCreatedId: "##newRecordId##" }
}
]
Kombiniert mit setNewRecordId
Beide Mechanismen können zusammen genutzt werden:
setNewRecordId | dataBag auf Create | |
|---|---|---|
| Zielfeld | Ninox-Referenzfeld (Verknüpfung) | JSON-Textfeld (State-Bag) |
| Schreibt | numerische ID direkt | ID als Patch-Key in JSON |
| Wann | Parent-Datensatz verlinken | UI-State nach Create setzen |
{
type: "create",
tableId: tableId("Aufgaben"),
changeFieldValues: [...],
setNewRecordId: [{ recordId: Nr, fieldId: fieldId(Nr, "LetzteAufgabe") }],
dataBag: {
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
base: helper_uiState,
patch: { selectedTaskId: "##newRecordId##" }
}
}
Kalender createNew (nach erfolgreichem Anlegen)
createNew.dataBag hängt direkt am Kalender-Create, nicht an einer Button-Action. Nach dem Anlegen (Aufziehen, Ganztags oder Ghost-Klick) läuft derselbe Create-Pfad. Typisch: Auswahl leeren, damit der Placement-Ghost verschwindet.
createNew: {
tableId: tableId("Termine"),
dateFrom: fieldId(first(Termine), "Datum von"),
timeFrom: fieldId(first(Termine), "Von"),
dateTo: fieldId(first(Termine), "Datum bis"),
timeTo: fieldId(first(Termine), "Bis"),
dataBag: {
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
base: helper_uiState,
patch: { auswahlId: null, lastTerminId: "##newRecordId##" }
}
}
Beispiele
Tab wechseln (Button)
arcCustomButton({
uniqueId: "tab-details-" + Nr,
title: "Details",
actions: [{
type: "update",
recordId: Nr,
fieldId: fieldId(Nr, "helper_uiState"),
dataBag: {
base: helper_uiState,
patch: { activeTab: "details" }
}
}]
})
Mobile Dimmer schließen (Layout-Block)
blocks: [{
width: "100%", height: "100%",
styles: "position: absolute; z-index: 100;",
value: "",
actions: [{
type: "update",
recordId: Nr,
fieldId: fieldId(Nr, "helper_mainContentState"),
dataBag: {
base: helper_mainContentState,
patch: { sidebarOpen: "", selectedShape: "" }
}
}]
}]
Shape auswählen → Sidebar (Drawing)
actions: [{
type: "update",
recordId: stateRecordId,
fieldId: stateField,
dataBag: {
base: helper_mainContentState,
patch: { selectedShape: Nr }
}
}]
Ausführlich: <a href="../widgets/custom-drawing.md"><code>custom-drawing</code></a> (Shape-actions, Klick vs. Drag).
Legacy: urlEncoded
Früher wurde JSON oft mit urlEncode(formatJSON(...)) geschrieben, weil Ninox-Actions mit {} in value problematisch waren. Mit dataBag (Patch im Bundle) ist das in neuen Projekten nicht mehr nötig.
raw (Default) | urlEncoded (Legacy) | |
|---|---|---|
| Im Feld | {"activeTab":"details"} | %7B%22activeTab%22%3A... |
| Formel lesen | parseJSON(text(feld)) | parseJSON(urlDecode(text(feld))) |
| Action schreiben | dataBag: { base, patch } | zusätzlich encoding: "urlEncoded" |
| Wann | neue Demos, Seeds, Trigger | bestehende Kundendaten |
Siehe auch
- <a href="/docs/ninox/templates/tips_statefield">State-Bag in einem Textfeld</a> — State-Bag manuell mit Ninox
setItem/item - <a href="../widgets/custom-drawing.md"><code>custom-drawing</code></a> — Widget-
dataBagfür Pin-Geometrie + Shape-Actions