# Templates

> Stel een documentboom samen met TSX, en bereken structuur met dezelfde JavaScript die je overal schrijft.

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

Een template benoemt een documentboom zodat een project ernaar kan wijzen. Niets
ervan wordt gerenderd wanneer het geschreven wordt — het evalueren van de JSX
bouwt alleen elementen, en dat is wat een component toelaat later te beslissen
wat hij is, met data in de hand.

## De vorm

```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>,
);
```

Wat die JSX aan Docxcelerate koppelt in plaats van aan React, is
`tsconfig.json`:

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

In aangemaakte workspaces staat dit al, dus bovenaan een bestand hoeft niets bij.
Voeg je Docxcelerate toe aan een project dat `jsxImportSource` ergens anders naar
laat wijzen, dan overschrijft een commentaar
`/** @jsxImportSource docxcelerate/template */` op regel één dat voor dat
bestand.

## Berekende structuur

Een template staat op modulescope, waar nog geen data bestaat, dus structuur die
van data afhangt hoort in een component en niet in het 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 wat falsy is bij de children wordt overgeslagen, dus `{condition && <Node
/>}` leest zoals overal elders. Nodes zonder id krijgen er een van waar ze staan,
en dat weerhoudt een `map` ervan namen te eisen die je niet hebt.

Dezelfde `.map()` werkt als de lijst pas per verzoek bekend is. Hier loopt hij door
de collectie, en bij publiceren bereikt hij de engine als lus — zie [lussen worden
als lussen gepubliceerd](#lussen-worden-als-lussen-gepubliceerd) hieronder.

## Herbruikbare onderdelen

Een component is een gewone functie, dus een omhulsel in huisstijl ook.
Componenten accepteren children net als al het 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>
);
```

Een gedeelde hook is de andere helft hiervan. Doordat `useSetPrompts` prompts zet
op wat de aanroepende component oplevert, kan huisstijl worden toegepast op een
node die de hook niet eens bezit:

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

Elke component die `useHouseVoice()` aanroept, krijgt die systeemprompt op de
node die hij teruggeeft, en kan hem nog steeds met een prop overschrijven.

## Documentprojecten

Een documentproject bindt een template via één ingang, `document.project.ts`, aan
zijn stijl en previewdata:

```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",
  },
});
```

`previewData` is waartegen de preview-app oplost. Houd het realistisch — korte
namen en verzonnen plaatsnamen verbergen lay-outproblemen die echte data juist
blootleggen.

## Wat er verandert bij publiceren

Alles hierboven gaat ervan uit dat jij de data hebt. Wanneer je bouwt met data in
handen — een preview, een lokale pack, een document dat je ter plekke schrijft —
betekent een `if` precies wat er staat, en loopt een `.map()` door de lijst die hij
kreeg.

Publiceren naar [een engine](/nl/docs/generation/endpoint/) is iets anders. Het
artefact wordt **één keer** gebouwd, tegen vervangende waarden voor een aanvraag
die nog niemand heeft gedaan, en opgeslagen. Een beslissing die van
aanvraaggegevens afhangt, kan tijdens die build niet worden genomen; die moet naar
de engine reizen en per document worden gemaakt.

Daaruit volgen drie regels, en de build handhaaft alle drie in plaats van een
verkeerd document bij een ontvanger te laten belanden.

### Reken per document, niet per build

Een waarde interpoleren werkt bij publiceren: het wordt het token dat de engine
vervangt.

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

Erop rekenen niet, want de waarde is nog niet bekend. Een bedrag opmaken, een lijst
optellen, een datum vergelijken — daar is een **deriver** voor nodig, die de engine
per document uitvoert:

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

`useFormat` en `useDeriver` rekenen in plaats daarvan tijdens de build, wat juist is
zodra de waarde al bekend is. Grijp naar een deriver wanneer dat niet zo is.

### Lussen worden als lussen gepubliceerd

Een vertakking heeft twee takken en beide kunnen worden gepubliceerd. Een lus heeft
er zoveel als de aanvraag items heeft, en dat aantal weet niemand tot er een document
geschreven wordt — dus wat er opgeslagen wordt, is de lus zelf. Wat je schrijft is de
gewone `.map()`:

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

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

Met echte data is dit gewoon de `.map()` uit de standaardbibliotheek: hij loopt
meteen door de collectie, dus previews tonen de herhaling in plaats van een
beschrijving ervan. Publiceren kan dat niet, dus onderschept de plaatsvervanger
diezelfde aanroep, draait de body één keer tegen een plaatsvervanger voor één item,
en bereikt de lus de engine ongeschonden.

Niemand schrijft hier een `{{ctx…}}`-token. Het item dat de body kreeg, kent het pad
waar het voor staat, dus de verwijzing schrijft zichzelf — en de rondes worden op
beide paden op positie benoemd, zodat een id hetzelfde betekent in een preview als in
het document dat iemand ontvangt.

Alles wat eerst naar de items moet kijken voordat de body geschreven kan worden —
`.filter()`, `.sort()`, `.length`, een `for`-lus — is bij het publiceren niet te
beantwoorden. Dat verwijst naar een deriver, die draait waar de data staat.

### Vertakkingen vermenigvuldigen

Een gepubliceerde vertakking slaat beide takken op, elk onder de conditie die hem
selecteert. Vertakkingen nesten vermenigvuldigt wat een document meedraagt, dus er is
een limiet — `branchLimit`, standaard 32 — en eroverheen gaan laat de build falen in
plaats van een stilletjes enorm artefact te produceren. Til de beslissing op naar een
deriver die één waarde oplevert, of verhoog de limiet als het document werkelijk zo
voorwaardelijk is.
