Naar de inhoud
Docxcelerate

Nodes

Tabel

Een raster van cellen, met kolommen die één keer worden gedeclareerd en rijen die gewone nodes zijn.

The columns belong to the table, because every row shares them — a table whose columns do not line up is not a table. Everything else is an ordinary node: a `.map()` produces rows, a condition drops one, and each names itself. That is what lets a published invoice carry one loop the engine walks rather than a table full of special cases.

Helpers
Table, Row, Cell
Nodesoort
table
Categorie
Data
Wordt opgelost
Static
Children
`Row`s, and any `.map()` producing them. A `Row` holds `Cell`s.
Optie Type Wat het doet
id string Stable address for the node. Engines target it and build artifacts diff on it, so treat a rename as a breaking change. Optional: a node without one takes an id from where it sits, which is what keeps branches and loops from forcing you to invent names. Two nodes claiming one id is an error rather than a race.
columns verplicht TableColumn[] The columns, left to right. Each takes a width in millimetres or "auto" to share what the fixed ones leave, and an align of left, center or right.
variant string A block style the theme looks up — "band", "badge", "panel". Names what the node is, never what it looks like: the appearance lives in the style's blocks, so a document restyles without a node changing. A name the theme has not heard of draws as an ordinary block rather than failing.
derivers DeriverInvocation[] Values the engine computes before the node resolves, written to derived.* and readable from a template token. Built with derive(). These survive publishing and run per document — use them for anything computed from request data. useDeriver runs one during the build instead.

Zo schrijf je er een

import { Cell, Row, Table, useFormat, useState } from "docxcelerate/template";

export const VisitLog: Table = () => {
  const { number } = useFormat("en-GB");
  const [state] = useState((data: MemberData) => ({ months: data.visitsByMonth }));

  return (
    <Table id="visit-log" columns={[{ width: "auto" }, { width: 28, align: "right" }]}>
      <Row header>
        <Cell>Maand</Cell>
        <Cell>Bezoeken</Cell>
      </Row>
      {state.months.map((entry) => (
        <Row>
          <Cell>{entry.month}</Cell>
          <Cell>{number(entry.visits)}</Cell>
        </Row>
      ))}
    </Table>
  );
};

De kolommen horen bij de tabel, in millimeters of "auto" om te verdelen wat de vaste overlaten, elk met een optionele align. Ze worden één keer gedeclareerd omdat elke rij ze deelt: een tabel waarvan de kolommen niet uitlijnen is geen tabel.

Al het andere is een gewone node. Een .map() maakt rijen, een voorwaarde laat er een weg, en elke noemt zichzelf. Dat is wat een gepubliceerde factuur één lus laat dragen die de engine doorloopt, in plaats van een tabel vol uitzonderingen.

Wat een cel bevat

Tekst gaat er rechtstreeks in, dus <Cell>{line.qty}</Cell> is het gewone geval en leest ook zo. Geef een cel alinea’s wanneer één regel niet genoeg is — een omschrijving boven een gedempte notitie — en ze blijven op aparte regels.

Koprijen

header op een rij tekent hem als kop. Alleen de rijen waarmee een tabel begint herhalen op elke nieuwe pagina, dus een totaalrij met header wordt als kop getekend maar blijft staan waar hij staat, onder de cijfers die hij optelt.

Varianten

Rows from data

src/nodes/table/basic.node.tsx

A header row, then one row per entry from a `.map()`.

import { Cell, Row, Table, useFormat, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";

/**
 * The columns are declared once, on the table, because every row shares them.
 * A row is an ordinary node, so a `.map()` produces one per entry and the
 * table needs to know nothing about loops.
 */
export const VisitLog: Table = () => {
  const { number } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({ months: data.visitsByMonth }));

  return (
    <Table id="visit-log" columns={[{ width: "auto" }, { width: 28, align: "right" }]}>
      <Row header>
        <Cell>Month</Cell>
        <Cell>Visits</Cell>
      </Row>
      {state.months.map((entry) => (
        <Row>
          <Cell>{entry.month}</Cell>
          <Cell>{number(entry.visits)}</Cell>
        </Row>
      ))}
    </Table>
  );
};
table · rows from data Open ↗
Waartoe het wordt opgelost

De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.

{
  "id": "visit-log",
  "kind": "table",
  "columns": [
    {
      "width": "auto"
    },
    {
      "width": 28,
      "align": "right"
    }
  ],
  "children": [
    {
      "id": "table-row",
      "kind": "tableRow",
      "header": true,
      "children": [
        {
          "id": "table-cell",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Month"
            }
          ]
        },
        {
          "id": "table-cell-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Visits"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-0",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-0",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-0-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Apr"
            }
          ]
        },
        {
          "id": "table-cell-0-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-0-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "11"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-1",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-1",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-1-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "May"
            }
          ]
        },
        {
          "id": "table-cell-1-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-1-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "14"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-2",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-2-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-2-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Jun"
            }
          ]
        },
        {
          "id": "table-cell-2-3",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-2-3-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "9"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-3",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-3",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-3-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Jul"
            }
          ]
        },
        {
          "id": "table-cell-3-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-3-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "16"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-4",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-4",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-4-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Aug"
            }
          ]
        },
        {
          "id": "table-cell-4-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-4-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "18"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-5",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell-5",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-5-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "Sep"
            }
          ]
        },
        {
          "id": "table-cell-5-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-5-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "12"
            }
          ]
        }
      ]
    }
  ]
}

A closing row

src/nodes/table/totals.node.tsx

A cell holding two paragraphs, and a row marked as a heading.

import { Cell, Paragraph, Row, Table, useFormat, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";

/**
 * A cell takes text directly, and paragraphs when one line is not enough. The
 * closing row is marked `header` so it is drawn as one — it stays where it is,
 * because only the rows a table opens with repeat onto a new page.
 */
export const PriceSummary: Table = () => {
  const { currency } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({
    plan: data.plan,
    lastPrice: data.lastPrice,
    newPrice: data.newPrice,
  }));

  return (
    <Table id="price-summary" columns={[{ width: "auto" }, { width: 30, align: "right" }]}>
      <Row>
        <Cell>
          <Paragraph>{state.plan}</Paragraph>
          <Paragraph>Last year</Paragraph>
        </Cell>
        <Cell>{currency(state.lastPrice)}</Cell>
      </Row>
      <Row header>
        <Cell>From renewal</Cell>
        <Cell>{currency(state.newPrice)}</Cell>
      </Row>
    </Table>
  );
};
table · a closing row Open ↗
Waartoe het wordt opgelost

De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.

{
  "id": "price-summary",
  "kind": "table",
  "columns": [
    {
      "width": "auto"
    },
    {
      "width": 30,
      "align": "right"
    }
  ],
  "children": [
    {
      "id": "table-row",
      "kind": "tableRow",
      "children": [
        {
          "id": "table-cell",
          "kind": "tableCell",
          "children": [
            {
              "id": "paragraph",
              "kind": "paragraph",
              "mode": "static",
              "text": "Peak Anytime"
            },
            {
              "id": "paragraph-2",
              "kind": "paragraph",
              "mode": "static",
              "text": "Last year"
            }
          ]
        },
        {
          "id": "table-cell-2",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-2-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "£468.00"
            }
          ]
        }
      ]
    },
    {
      "id": "table-row-2",
      "kind": "tableRow",
      "header": true,
      "children": [
        {
          "id": "table-cell-3",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-3-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "From renewal"
            }
          ]
        },
        {
          "id": "table-cell-4",
          "kind": "tableCell",
          "children": [
            {
              "id": "table-cell-4-text",
              "kind": "paragraph",
              "mode": "static",
              "text": "£492.00"
            }
          ]
        }
      ]
    }
  ]
}

Aantekeningen

  • Een lege cel is nog steeds een cel. Word accepteert geen lege, dus hij wordt als blanco vak ingepakt in plaats van als ontbrekende kolom.
  • span laat een cel over meerdere kolommen lopen; align overschrijft de uitlijning van de kolom voor alleen die cel.
  • Een variant op een cel wint van die op zijn rij, en die van die op de tabel — de smallere uitspraak is de bewustere.
  • Beide meegeleverde renderers tekenen een echte tabel: een <table> op het scherm en een Word-tabel met de gedeclareerde kolombreedtes in de .docx.

Deze pagina bewerken op GitHub ↗ Deze pagina als Markdown lezen