# Templates

> Einen Dokumentbaum mit TSX zusammensetzen — und Struktur mit demselben JavaScript berechnen, das Sie überall sonst schreiben.

Source: https://docxcelerate.com/de/docs/essentials/templates/

Ein Template benennt einen Dokumentbaum, damit ein Projekt darauf zeigen kann.
Beim Schreiben wird nichts davon gerendert — das Auswerten des JSX baut nur
Elemente, und genau das erlaubt es einer Komponente, später zu entscheiden, was
sie ist, mit Daten in der Hand.

## Die Form

```tsx
import { Document, Section, template } from "docxcelerate/template";
import { Greeting, NextSteps } from "./nodes/index.ts";
import type { DocumentData } from "./types.ts";

export const documentTemplate = template<DocumentData>(
  <Document id="tenancy-renewal" title="Tenancy Renewal">
    <Section id="opening" title="Opening">
      <Greeting />
    </Section>
    <Section id="closing" title="Closing">
      <NextSteps />
    </Section>
  </Document>,
);
```

Was dieses JSX mit Docxcelerate statt mit React verdrahtet, ist die
`tsconfig.json`:

```json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "docxcelerate/template"
  }
}
```

In angelegten Workspaces steht das bereits, oben in einer Datei ist also nichts
zu ergänzen. Wenn Sie Docxcelerate zu einem Projekt hinzufügen, das
`jsxImportSource` anderswohin zeigen lässt, überschreibt ein Kommentar
`/** @jsxImportSource docxcelerate/template */` in Zeile eins das für diese
Datei.

## Berechnete Struktur

Ein Template steht auf Modulebene, wo es noch keine Daten gibt. Struktur, die
von Daten abhängt, gehört deshalb in eine Komponente, nicht in das Template:

```tsx
export const Enclosures: Section = () => {
  const [state] = useState((data: DocumentData) => ({ items: data.enclosures }));

  return (
    <Section id="enclosures" title="Enclosed">
      {state.items.map((item) => <Paragraph>{item.label}</Paragraph>)}
    </Section>
  );
};
```

Alles Falsy unter den Kindern wird übersprungen, `{condition && <Node />}` liest
sich also wie überall sonst. Nodes ohne id bekommen eine aus ihrer Position, und
das hindert ein `map` daran, Namen zu verlangen, die Sie nicht haben.

Dasselbe `.map()` funktioniert auch, wenn die Liste erst je Anfrage feststeht.
Hier läuft es über die Sammlung, und beim Veröffentlichen erreicht es die Engine
als Schleife — siehe [Schleifen werden als Schleifen
veröffentlicht](#schleifen-werden-als-schleifen-veröffentlicht) weiter unten.

## Wiederverwendbare Teile

Eine Komponente ist eine gewöhnliche Funktion, eine Hülle im Hausstil also
ebenso. Komponenten nehmen Kinder wie alles andere:

```tsx
export const Letterhead: Section<{ children?: Yield }> = ({ children }) => (
  <Section id="letterhead" title="">
    <Image id="logo" src="assets/logo.png" alt="Ashcroft Housing" />
    {children}
  </Section>
);
```

Ein geteilter Hook ist die andere Hälfte davon. Weil `useSetPrompts` Prompts an
dem Node setzt, den die aufrufende Komponente liefert, lässt sich Hausstil auf
einen Node anwenden, der dem Hook gar nicht gehört:

```tsx
export function useHouseVoice() {
  useSetPrompts({
    systemPrompt: "You write for a housing association. Plain English, second person.",
  });
}
```

Jede Komponente, die `useHouseVoice()` aufruft, bekommt diesen System-Prompt an
dem Node, den sie zurückgibt — und kann ihn per Prop weiterhin überschreiben.

## Dokumentprojekte

Ein Dokumentprojekt bindet ein Template über einen einzigen Entrypoint,
`document.project.ts`, an seinen Stil und seine Vorschaudaten:

```tsx
import { defineDocumentProject } from "docxcelerate/document";
import { documentTemplate } from "./document.tsx";
import { documentStyle } from "./document-style.ts";
import type { DocumentData } from "./types.ts";

export default defineDocumentProject<DocumentData>({
  id: "tenancy-renewal",
  name: "Tenancy Renewal",
  version: "1.0.0",
  template: documentTemplate,
  style: documentStyle,
  previewData: {
    residentName: "Avery Mitchell",
    propertyRef: "Flat 4, Ashcroft House",
  },
});
```

Gegen `previewData` löst die Vorschau-App auf. Halten Sie es realistisch — kurze
Namen und erfundene Ortsnamen verdecken Layout-Probleme, die echte Daten
sichtbar machen.

## Was sich beim Veröffentlichen ändert

Alles oben setzt voraus, dass Sie die Daten haben. Wenn Sie mit Daten in der Hand
bauen — eine Vorschau, ein lokales Pack, ein spontan geschriebenes Dokument —,
bedeutet ein `if` genau das, was dasteht, und ein `.map()` läuft über die Liste,
die es bekommen hat.

An [eine Engine](/de/docs/generation/endpoint/) zu veröffentlichen ist etwas
anderes. Das Artefakt wird **einmal** gebaut, gegen Stellvertreterwerte für eine
Anfrage, die noch niemand gestellt hat, und gespeichert. Eine Entscheidung, die
von Anfragedaten abhängt, kann bei diesem Build nicht fallen; sie muss zur Engine
reisen und je Dokument getroffen werden.

Daraus folgen drei Regeln, und der Build erzwingt alle drei, statt ein falsches
Dokument bis zur Empfängerin oder zum Empfänger durchzulassen.

### Je Dokument rechnen, nicht je Build

Einen Wert zu interpolieren funktioniert beim Veröffentlichen: Er wird zu dem
Token, das die Engine ersetzt.

```tsx
<Paragraph id="greeting">Hello {state.name},</Paragraph>
// veröffentlicht als: "Hello {{data.name}},"
```

Auf ihm zu rechnen nicht, denn der Wert ist noch nicht bekannt. Eine Währung
formatieren, eine Liste summieren, ein Datum vergleichen — dafür braucht es einen
**Deriver**, den die Engine je Dokument ausführt:

```tsx
<Paragraph
  id="balance"
  derivers={[
    derive("currencyLabel", { output: "balanceLabel", inputs: [dataRef("balanceDue")] }),
  ]}
>
  Your balance is {"{{derived.balanceLabel}}"}.
</Paragraph>
```

`useFormat` und `useDeriver` rechnen stattdessen während des Builds, was richtig
ist, wann immer der Wert schon bekannt ist. Zum Deriver greifen Sie, wenn er es
nicht ist.

### Schleifen werden als Schleifen veröffentlicht

Eine Verzweigung hat zwei Arme, und beide lassen sich veröffentlichen. Eine
Schleife hat so viele, wie die Anfrage Einträge hat, und diese Zahl kennt niemand,
bevor ein Dokument geschrieben wird — gespeichert wird also die Schleife selbst.
Geschrieben wird dafür das gewöhnliche `.map()`:

```tsx
const [visits] = useState((data: TenancyData) => data.visits);

return visits.map((visit) => <Paragraph id="visit">Visit: {visit.label}</Paragraph>);
```

Mit echten Daten ist das schlicht das `.map()` aus der Standardbibliothek: es
läuft sofort über die Sammlung, Vorschauen zeigen also die Wiederholung statt
einer Beschreibung davon. Beim Veröffentlichen geht das nicht, also fängt der
Platzhalter denselben Aufruf ab, lässt den Rumpf einmal gegen einen Platzhalter
für einen Eintrag laufen, und die Schleife erreicht die Engine unversehrt.

Ein `{{ctx…}}`-Token schreibt hier niemand. Der Eintrag, den der Rumpf bekommen
hat, kennt den Pfad, für den er steht, also schreibt sich die Referenz selbst —
und die Durchläufe werden auf beiden Wegen nach ihrer Position benannt, damit eine
id in der Vorschau dasselbe bedeutet wie im Dokument, das eine Empfängerin erhält.

Alles, was die Einträge erst ansehen muss, bevor der Rumpf geschrieben werden kann
— `.filter()`, `.sort()`, `.length`, eine `for`-Schleife — lässt sich beim
Veröffentlichen nicht beantworten. Das verweist auf einen Deriver, der dort läuft,
wo die Daten sind.

### Verzweigungen multiplizieren

Eine veröffentlichte Verzweigung speichert beide Arme, jeden unter der Bedingung,
die ihn auswählt. Verschachtelte Verzweigungen multiplizieren, was ein Dokument
mitträgt, deshalb gibt es ein Limit — `branchLimit`, standardmäßig 32 — und es zu
überschreiten lässt den Build scheitern, statt ein still und leise riesiges
Artefakt zu erzeugen. Heben Sie die Entscheidung in einen Deriver, der einen Wert
liefert, oder erhöhen Sie das Limit, wenn das Dokument wirklich so bedingt ist.
