Skip to content

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.

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.

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).

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.

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.