Writing the view
The view of a template renders the content of the document. It receives
the data of the document as variables – what the action passed to
create() – and is rendered into the layout of Anvil.
<?php/** * @var array{name: string, personnel_number: string|null} $person * @var list<array{title: string, serial: string|null}> $items * @var string|null $note */?><h1>Issue receipt</h1><p class="muted">The following items were handed over for business use.</p>
<table class="facts"> <tr><th>Recipient</th><td><strong><?= esc($person['name']) ?></strong></td></tr><?php if ($person['personnel_number'] !== null): ?> <tr><th>Personnel number</th><td><?= esc($person['personnel_number']) ?></td></tr><?php endif ?></table>
<h2>Items</h2><table class="data"> <thead><tr><th>#</th><th>Item</th><th>Serial number</th></tr></thead> <tbody><?php foreach ($items as $index => $item): ?> <tr><td><?= $index + 1 ?></td><td><?= esc($item['title']) ?></td><td><?= esc($item['serial'] ?? '–') ?></td></tr><?php endforeach ?> </tbody></table>
<?php if (($note ?? '') !== ''): ?><div class="box">Note: <?= esc($note) ?></div><?php endif ?>Escape every value with esc(): the data comes from records and users.
Building blocks of the layout
Section titled “Building blocks of the layout”| Element / class | Use |
|---|---|
h1 |
title of the document |
h2 |
section headings |
p |
text |
table.facts |
label/value pairs: <th> label, <td> value |
table.data |
lists of items with a header row; tr.total for a sum row |
.box |
a framed note, e.g. terms |
.muted, .small |
secondary text |
The layout adds around the content:
- Header: logo or name (
$documentBrand), title, document number and date. - Footer: name, document number and page number.
- Signature block (templates with signature): the confirmation text, the signature image, the name of the signer and the time; below it a verification line with the content hash.
Page format: A4 portrait, margins 32/18/24/18 mm, font DejaVu Sans 9.5 pt.
What the PDF engine supports
Section titled “What the PDF engine supports”The PDF engine is dompdf: HTML and CSS 2.1 with parts of CSS 3.
- No flexbox or grid – use tables for columns.
- No remote resources: images, fonts and stylesheets from URLs are
never loaded. Embed images as data URIs (
data:image/png;base64,…). - No JavaScript, no PHP in the HTML.
- Page breaks:
page-break-before: always,page-break-inside: avoid(the signature block uses it). - Fonts are bundled (DejaVu Sans, covering umlauts and most European scripts).
Same content in browser and PDF
Section titled “Same content in browser and PDF”The same view is shown on the signing page in the browser (with the styles of the panel), so the signer sees exactly the content of the PDF. The signature is bound to the SHA-256 of this HTML; see Signing.
Keep the view deterministic: render only the data of the document, not the current time, random values or data loaded in the view – otherwise the content changes between showing and signing, and the signature is rejected.
Changing a view
Section titled “Changing a view”Drafts are rendered from their data whenever they are shown, so a changed
view changes the drafts as well; signed documents keep their PDF. Keep
views compatible with the data of existing drafts (e.g. check for keys
added later), and raise version() when the change matters.