Skip to content
Trilha
Chapters

Reference

Package tmpl

Use html/template inside the pages and layouts pipeline.

import "github.com/emersonjoe/trilha/tmpl"
FunctionDescription
Node(t *template.Template, name string, data any) h.Nodenode that executes the named template; an execution error becomes a render error (500), with no partial output
Must(fsys fs.FS, patterns ...string) *template.Templatetemplate.ParseFS that panics on error; call it at package level to fail at startup
Wrap(t *template.Template, name, slot string) *Shellprepares a shell template to receive an h.Node where it calls {{template slot .}}; call it at package level — it clones the set, and html/template only clones a set that has not executed yet
(*Shell) Node(data any, children h.Node) h.Noderenders the shell with data and children in the slot; a shell that never reaches the slot is a render error
HTML(n h.Node) (template.HTML, error)the node as template.HTML, for data given to a template the app executes itself

Usage#

//go:embed *.html
var files embed.FS

var t = tmpl.Must(files, "*.html")

func Page(c *trilha.Ctx) (h.Node, error) {
	c.SetTitle("Report")
	return tmpl.Node(t, "report", data), nil
}

The template uses {{define "report"}}...{{end}} and receives data as .. Escaping is the contextual escaping of html/template. The node can be combined with the DSL: h.Section(h.Class("x"), tmpl.Node(t, "part", d)).

The other direction: an h.Node inside a template#

The shell of an app that already exists in html/template, with the new screens written in h, is the halfway house of every migration. Wrap ties the two together:

var shell = tmpl.Wrap(tmpl.Must(files, "*.html"), "shell", "content")

func Layout(c *trilha.Ctx, children h.Node) (h.Node, error) {
	return shell.Node(page(c.Request()), children), nil
}

The template keeps the shape it had — the slot is the {{template "content" .}} that was already there — and nothing in the app converts anything to template.HTML: what h rendered was escaped on the way in, and tmpl is the single place that says so. The data of the shell can be built from the *http.Request alone, including trilha.NonceFrom(r) and trilha.CSRFTokenFrom(r). examples/blog has the whole thing in app/legado-.

Wrap clones the set, so call it at package level: html/template refuses to clone a set that has already executed. A shell that never reaches the slot — a {{if}} that hid it, the wrong slot name — fails the render with tmpl: template %q never rendered the slot instead of quietly answering a page with no content.

HTML(n) is the low-level way out, for a template the app executes itself.