---
title: "Data Bag Actions"
slug: "data-bag-actions"
category: "Patterns"
hosts: "ninox"
---

# Data Bag Actions (partielle Updates in JSON-Töpfen)

## AI Defaults (read first)

- **`createNew.dataBag`** (Calendar Week) = nach erfolgreichem Anlegen, inkl. `##newRecordId##`. Siehe [`custom-calendar-week`](../widgets/custom-calendar-week.md).
- **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`](../widgets/custom-drawing.md#-databag).

---

## 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 [State-Bag in einem Textfeld](/docs/ninox/templates/tips_statefield)).

Mit **`dataBag` auf der Action** übernimmt der Action Performer Read → Patch → Write.

## Action-Syntax

### Partiell patchen (empfohlen für State-Bags)

```javascript
{
  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)

```javascript
{
  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.

```javascript
{
  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)

```javascript
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 |

```javascript
{
  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.

```javascript
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)

```javascript
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)

```javascript
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)

```javascript
actions: [{
  type: "update",
  recordId: stateRecordId,
  fieldId: stateField,
  dataBag: {
    base: helper_mainContentState,
    patch: { selectedShape: Nr }
  }
}]
```

Ausführlich: [`custom-drawing`](../widgets/custom-drawing.md) (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

- [State-Bag in einem Textfeld](/docs/ninox/templates/tips_statefield) — State-Bag manuell mit Ninox `setItem` / `item`
- [`custom-drawing`](../widgets/custom-drawing.md) — Widget-`dataBag` für Pin-Geometrie + Shape-Actions
