# Dynamic Form — Config-Template-Struktur

Dieses Dokument beschreibt den Aufbau des JSON-`config_template`-Felds, das dynamische Formulare in der Anwendung steuert.

Die Verarbeitungslogik ist im Trait `ProcessesConfigTemplate` implementiert (`src/Traits/ProcessesConfigTemplate.php`). Der Trait wandelt das rohe JSON in
eine formular-bereite Datenstruktur um, die von der Blade-Komponente `<x-globals::dynamic-form.form />` gerendert wird.

**Ursprüngliche Implementierung:** `ExternalSystems` (Verwaltung von Drittsystemanbindungen). Von dort auf `PrinterTypes` übertragen.

---

## Formate

Das Template kann als **JSON-Array** (numerisch indiziert) oder als **JSON-Objekt** (assoziativ indiziert) angegeben werden.

### Array-Format

```json
[
    {
        "name": "host"
    },
    {
        "name": "port"
    }
]
```

Das `name`-Attribut ist bei nummerischer Indizierung **zwingend** erforderlich.

### Objekt-Format

```json
{
    "host": {
        "name": "host"
    },
    "port": {
        "name": "port"
    }
}
```

Bei assoziativem Format ist das `name`-Attribut **optional** — der Schlüssel wird als Name verwendet, wenn `name` fehlt.

Das Objekt-Format ist die empfohlene Variante, da es das optionale `.meta`-Element (Mehrsprachigkeit, Sortierung) unterstützt.

---

## Feldattribute

Jedes Feldobjekt kann folgende Attribute enthalten:

| Attribut      | Typ      | Pflicht | Standard        | Beschreibung |
|---------------|----------|---------|-----------------|--------------|
| `name`        | string   | *       | —               | Feldbezeichner. Pflicht im Array-Format, optional im Objekt-Format. |
| `type`        | string   | nein    | `string`        | Typ des Eingabefelds. Siehe [Feldtypen](#feldtypen). |
| `label`       | string   | nein    | Wert von `name` | Abweichende Beschriftung des Felds. Wird von `.meta.labels` überschrieben. |
| `default`     | mixed    | nein    | `null`          | Vorausgefüllter Wert bei der Neuanlage. |
| `fullwidth`   | bool     | nein    | `false`         | Feld nimmt die volle Breite des Formulars ein (`sm:col-span-2`). |
| `mandatory`   | bool     | nein    | `false`         | Pflichtfeld-Markierung (visuell + Validierung). |
| `section`     | string   | nein    | —               | Sektion in der das Feld erscheint. Siehe [Sektionen](#sektionen). |
| `order`       | integer  | nein    | —               | Sortierposition innerhalb des Formulars. Wird ignoriert wenn `.meta.order` vorhanden ist. |
| `crypted`     | 0 \| 1   | nein    | `0`             | Gespeicherter Wert wird verschlüsselt. |
| `subtype`     | string   | nein    | —               | Sub-Typ für zukünftige Validierungssteuerung (noch nicht implementiert). |
| `options`     | array    | *       | —               | Pflicht bei `type: "select"`. Liste der auswählbaren Werte. |
| `placeholder` | string   | nein    | —               | Platzhaltertext (`string`, `text`, `number`, `textarea`). |
| `rows`        | integer  | nein    | `3`             | Zeilenhöhe. Nur bei `type: "textarea"`. |
| `min`         | number   | nein    | —               | Minimalwert. Nur bei `type: "number"`. |
| `max`         | number   | nein    | —               | Maximalwert. Nur bei `type: "number"`. |
| `step`        | number   | nein    | —               | Schrittweite (z.B. `0.5`, `10`). Nur bei `type: "number"`. |
| `info`        | string   | nein    | —               | Beschreibungstext zum Feld. Wird als kleines (i)-Icon neben dem Label angezeigt; beim Mouseover öffnet sich ein Tooltip mit dem Text. *(geplant, noch nicht implementiert)* |

---

## Feldtypen

Der `type`-Wert bestimmt das gerenderte Eingabeelement:

| Typ        | HTML-Element | Hinweis |
|------------|-------------|---------|
| `string`   | `<input type="text">` | Standard, wenn `type` fehlt |
| `text`     | `<input type="text">` | Alias für `string` |
| `number`   | `<input type="number">` | Unterstützt `min`, `max`, `step` |
| `textarea` | `<textarea>` | Unterstützt `rows` (Standard: 3) |
| `toggle`   | Checkbox-Toggle | Wert: `true` / `false` |
| `select`   | `<select>` | Erfordert `options: [...]` |
| `spacer`   | Leeres `<div>` | Kein Wert, kein Label. Wird nicht als Felddefinition angegeben, sondern ausschließlich über `_`-Platzhalter in `.meta.order`. Siehe [Spacer und `fullwidth`](#spacer-und-fullwidth). |

---

## Sektionen

Mit dem `section`-Attribut können Felder in Gruppen eingeteilt werden.
Die Darstellung der Gruppen wird durch den `display`-Parameter der Blade-Komponente gesteuert (`"sections"` oder `"tabs"`).

Das `section`-Attribut ist optional. Felder ohne `section`-Angabe verhalten sich wie folgt:
* Sind **keine weiteren Sektionen** definiert (alle Felder ohne `section`): alle Felder landen in einer einzigen impliziten Gruppe ohne Überschrift (`display="sections"`) bzw. einem einzelnen Tab mit dem Standard-Label "Allgemein" (`display="tabs"`).
* Sind **andere Sektionen** definiert: Felder ohne `section` werden automatisch der ersten Sektion vorangestellt.

Das Standard-Label der impliziten Gruppe ist `globals::global.dynamic-form.default-section-label` und damit übersetzt. Es kann über `.meta.labels` mit dem Schlüssel `"section.default"` überschrieben werden.

### Einfache Sektionen

```json
{
    "host": {
        "section": "network"
    },
    "port": {
        "section": "network"
    },
    "username": {
        "section": "auth"
    },
    "password": {
        "section": "auth", "crypted": 1
    }
}
```

### Dotted-Notation für Sub-Sektionen

Im `display="tabs"`-Modus der Blade-Komponente wird automatisch erkannt, ob Section-Keys einen Punkt enthalten. Ist das der Fall, wird eine **Zwei-Ebenen-Hierarchie** erzeugt:

* Teil **vor** dem ersten Punkt → Tab (obere Ebene)
* Teil **nach** dem ersten Punkt → Sub-Sektion innerhalb des Tabs
* Section-Key **ohne** Punkt → Felder direkt im Tab, ohne Sub-Sektions-Überschrift

```json
{
    "host": {
        "section": "network.basic"
    },
    "port": {
        "section": "network.basic"
    },
    "timeout": {
        "section": "network.advanced"
    },
    "keepalive": {
        "section": "network.advanced"
    },
    "username": {
        "section": "auth"
    }
}
```

Ergibt: Tab **"network"** mit Sub-Sektionen *"basic"* und *"advanced"*, Tab **"auth"** mit Feldern ohne Sub-Sektions-Überschrift.

Labels für Sub-Sektionen folgen der `.meta.labels`-Konvention:
`"section.network.basic": "Grundeinstellungen"`.

---

## Das `.meta`-Element

Das optionale `.meta`-Element ist nur im **Objekt-Format** verfügbar und ermöglicht Mehrsprachigkeit und zentrale Sortiersteuerung.

```json
{
    ".meta": {
        "labels": "globals::printers.dynamic-form-labels",
        "order": [
            "host",
            "port",
            "username",
            "password"
        ],
        "sectionorder": [
            "network",
            "auth"
        ]
    },
    "host":     { ... },
    "port":     { ... }
}
```

### `.meta.labels`

Locale-abhängige Label-Übersetzungen. Zwei Varianten werden unterstützt:

#### Variante 1 — Translation-Key (empfohlen)

`labels` ist ein String im Laravel-Translation-Key-Format:

```json
".meta": {
    "labels": "globals::printers.dynamic-form-labels"
}
```

Die Labels werden über `trans()` aufgelöst. Laravel wählt automatisch die passende Locale. Die referenzierte Übersetzungsdatei enthält ein flaches Array:

```php
// lang/de/printers.php
'dynamic-form-labels' => [
    'host'             => 'Hostname',
    'port'             => 'Port',
    'section.network'  => 'Netzwerk',
    'section.auth'     => 'Authentifizierung',
],
```

**Vorteile:** Schlanke Config, neue Sprachen erfordern nur eine neue `lang/<locale>/`-Datei — keine Config-Änderungen nötig.

#### Variante 2 — Inline-Objekt (Legacy)

`labels` ist ein Objekt mit Locale-Codes als Schlüssel:

```json
".meta": {
    "labels": {
        "de": {
            "host": "Hostname",
            "section.network": "Netzwerk"
        },
        "en": {
            "host": "Hostname",
            "section.network": "Network"
        }
    }
}
```

Die Locale entspricht dem Laravel-App-Locale (`App::getLocale()`).

**Hinweis:** Diese Variante bläht die Config auf und erfordert Anpassungen bei jeder neuen Sprache. Für neue Configs wird Variante 1 empfohlen.

---

**Gemeinsame Konventionen (beide Varianten):**

* Feld-Labels: Key = Feldname (`"host"`)
* Sektion-Labels: Key = `"section.<sectionKey>"` (`"section.network"`, `"section.network.basic"`)

Ist kein Label definiert, wird der Feldname als Label verwendet. Lokale `label`-Attribute in Felddefinitionen werden von `.meta.labels` überschrieben.

### `.meta.order`

Globale Feldreihenfolge als Array von Feldnamen. Felder die nicht aufgelistet sind, werden ans Ende gestellt. Überschreibt individuelle `order`-Attribute in Felddefinitionen.

### `.meta.sectionorder`

Reihenfolge der Sektionen als Array von Section-Keys.

### `x.meta` — Deaktivierung ohne Löschung

Das `.meta`-Element kann temporär deaktiviert werden, indem es in `x.meta` umbenannt wird. Die Verarbeitungslogik überspringt `x.meta` genauso wie `.meta`.

```json
{
    "x.meta": {
        "labels": "...",
        "order": [...]
    },
    "host": { ... }
}
```

Nützlich beim Entwickeln und Testen einer Config, wenn das `.meta`-Element vorübergehend außer Kraft gesetzt werden soll.

---

## Vollständige Beispiele

### Minimal (Array-Format, ein Feld)

```json
[
    {
        "crypted": 1,
        "mandatory": true,
        "name": "api_key"
    }
]
```

### Flaches Objekt (alle Feldtypen)

```json
{
    "name": {
        "mandatory": true,
        "type": "string"
    },
    "timeout": {
        "default": 30,
        "type": "number"
    },
    "active": {
        "default": true,
        "type": "toggle"
    },
    "protocol": {
        "default": "https",
        "options": [
            "http",
            "https"
        ],
        "type": "select"
    },
    "notes": {
        "fullwidth": true,
        "type": "textarea"
    }
}
```

### Spacer und `fullwidth`

Spacer werden **nicht** als Felddefinitionen eingetragen, sondern ausschließlich über `_`-Platzhalter in `.meta.order` gesetzt. Da `.meta.order` ein Array ist (keine Objekt-Keys), können beliebig viele `_`-Einträge ohne Namenskonflikte verwendet werden.

**Zuordnungsregel:** Ein Spacer gehört immer zur Section des **nachfolgenden** Felds in der Order-Liste. Spacer ohne nachfolgendes Feld (am Ende der Liste oder gefolgt nur von weiteren `_`) werden verworfen.

Ohne Spacer stünde "Vorname" neben "Firma" und "Nachname" allein in der nächsten Zeile. Mit einem `_` vor "Vorname" bleibt die zweite Spalte der ersten Zeile frei, und Vorname/Nachname stehen zusammen.

```json
{
    ".meta": {
        "order": [
            "company",
            "_",
            "first_name",
            "last_name",
            "notes"
        ]
    },
    "company":    {
        "type": "string"
    },
    "first_name": {
        "type": "string"
    },
    "last_name":  {
        "type": "string"
    },
    "notes":      {
        "fullwidth": true,
        "rows": 5,
        "type": "textarea"
    }
}
```

Ergibt folgendes Layout (zweispaltiges Grid):

```
[ Firma          ] [               ]  ← _ gehört zu "first_name", hält Spalte 1 frei
[ Vorname        ] [ Nachname      ]
[ Notizen (volle Breite)           ]
```

Zwei `_` hintereinander halten eine komplette Zeile frei:

```json
"order": [
    "field_a",
    "_",
    "_",
    "field_b"
]
```

```
[ Field A        ] [               ]
[               ] [               ]  ← ganze Zeile frei
[ Field B        ] ...
```

Spacer sind nur bei Verwendung von `.meta.order` verfügbar. Ohne `.meta.order` (nur feldindividuelle `order`-Attribute) werden `_`-Einträge nicht unterstützt.

**Multi-Section-Configs:** Die Zuordnungsregel gilt auch wenn Felder mehrerer Sections in `.meta.order` gemischt sind. Ein `_` zwischen einem Feld aus Section A und einem Feld aus Section B erscheint in Section B — vor dem nachfolgenden Feld.

### Mit Sektionen und `.meta` (Sections-Modus)

```json
{
    ".meta": {
        "labels": "mypackage::my-module.dynamic-form-labels",
        "order": [
            "host",
            "port",
            "username",
            "password"
        ],
        "sectionorder": [
            "network",
            "auth"
        ]
    },
    "host":     {
        "mandatory": true,
        "section": "network"
    },
    "port":     {
        "default": 443,
        "section": "network",
        "type": "number"
    },
    "username": {
        "mandatory": true,
        "section": "auth"
    },
    "password": {
        "crypted": 1,
        "section": "auth"
    }
}
```

```php
// lang/de/my-module.php
'dynamic-form-labels' => [
    'host'             => 'Hostname',
    'port'             => 'Port',
    'username'         => 'Benutzername',
    'password'         => 'Passwort',
    'section.network'  => 'Netzwerk',
    'section.auth'     => 'Authentifizierung',
],
```

### Mit Sub-Sektionen (Tabs-Modus, automatische Erkennung)

```json
{
    ".meta": {
        "labels": {
            "de": {
                "host":                    "Hostname",
                "keepalive":               "Keep-Alive",
                "password":                "Passwort",
                "port":                    "Port",
                "section.auth":            "Authentifizierung",
                "section.network":         "Netzwerk",
                "section.network.advanced":"Erweitert",
                "section.network.basic":   "Grundeinstellungen",
                "timeout":                 "Timeout (s)",
                "username":                "Benutzername"
            }
        },
        "order": [
            "host",
            "keepalive",
            "password",
            "port",
            "timeout",
            "username"
        ],
        "sectionorder": [
            "network.basic",
            "network.advanced",
            "auth"
        ]
    },
    "host":      {
        "mandatory": true,
        "section": "network.basic"
    },
    "port":      {
        "default": 443,
        "section": "network.basic",
        "type": "number"
    },
    "timeout":   {
        "default": 30,
        "section": "network.advanced",
        "type": "number"
    },
    "keepalive": {
        "default": true,
        "section": "network.advanced",
        "type": "toggle"
    },
    "username":  {
        "section": "auth"
    },
    "password":  {
        "crypted": 1,
        "section": "auth"
    }
}
```

Ergibt im `display="tabs"`-Modus: Tab **"Netzwerk"** mit Sub-Sektionen *"Grundeinstellungen"* und *"Erweitert"*, Tab **"Authentifizierung"** ohne Sub-Sektions-Überschrift.

---

## Hinweise

* Das Array-Format unterstützt kein `.meta`-Element.
* `sectionorder` beeinflusst nur die Reihenfolge der Sektionen, nicht der Tabs bei dotted-Notation. Die Tab-Reihenfolge ergibt sich aus der Reihenfolge des ersten Auftretens des jeweiligen Tab-Präfixes in `sectionorder`.
* Der `crypted`-Mechanismus ist in der Feldverarbeitung vorbereitet, aber noch nicht vollständig implementiert.
* `subtype` ist reserviert für zukünftige Validierungssteuerung und wird aktuell nicht ausgewertet.
