Templates
Render HTML or text from templates.
On this page
valk.template renders HTML or text from templates at runtime. Create an engine, register templates, and render one with a data value.
Render a template#
use valk.template
class Page {
title: String
}
fn main() {
let views = template.Engine.new()
views.set("page.html", "<h1>{{ title }}</h1>")
let html = views.render("page.html", Page { title: "Notes & ideas" }) ! {
println("Could not render: " + E.code)
return
}
println(html) // <h1>Notes & ideas</h1>
}
{{ title }} prints a field of the data, HTML-escaped. Names reach deeper with . and [index], such as {{ user.name }} or {{ items[0] }}. A name that does not exist is an error. render_content(text, data) renders template text without registering it, and render_into(name, data, writer) writes to a file or response instead of returning a string.
Syntax#
{{ expr }} Print escaped
{! expr !} Print raw, without escaping
{# comment #}
@if(a > 1 && !b) @elif(c) @else @end
@each(items as item) ... @empty ... @end @empty renders when the list is empty
@each(map as value, key, index) @end
@set(name = expr)
@include("header.html") or @include("card.html", item) with other data
@extend("layout.html") @block("content") ... @end
@yield("content") In a layout: where a block goes
@isset(x) @len(x) In expressions
Expressions support ==, !=, <, <=, >, >=, &&, ||, !, parentheses, and + - * / %; + with text joins it. A directive alone on its line takes the line with it, so loops leave no blank lines. Write \@if or \{{ for literal text.
Loops, conditions and includes#
use valk.template
class Note {
title: String
done: bool
}
class Page {
notes: Array[Note]
}
fn main() {
let views = template.Engine.new()
views.set("item.html", "<li>{{ title }}@if(done) (done)@end</li>")
views.set("list.html", "<ul>\n@each(notes as note)\n@include(\"item.html\", note)\n@empty\n<li>Nothing yet</li>\n@end\n</ul>")
let data = Page { notes: Array[Note]{ Note { title: "Write docs", done: true } } }
println(views.render("list.html", data) ! return)
}
Layouts#
A page fills the blocks of a layout with @extend and @block; the layout prints them with @yield:
use valk.template
class Page {
title: String
}
fn main() {
let views = template.Engine.new()
views.set("layout.html", "<title>{{ title }}</title>\n<main>@yield(\"content\")</main>")
views.set("home.html", "@extend(\"layout.html\")\n@block(\"content\")Welcome@end")
println(views.render("home.html", Page { title: "Home" }) ! return)
}
Filters#
A filter changes a value on its way into the page, left to right: {{ summary | trim | truncate(80) }}.
| Filters | |
|---|---|
| Text | upper, lower, capitalize, trim, truncate(n, suffix), replace(from, to), escape, urlencode |
| Lists | length, join(sep), first, last, reverse, keys |
| Numbers | round(decimals), fixed(decimals) |
| Other | default("..."), json |
Register your own with set_filter. It gets the value and the arguments as json.Value:
use valk.template
use valk.json
class Item {
name: String
price: float
}
fn main() {
let views = template.Engine.new()
views.set_filter("money", fn(value: json.Value, args: Array[json.Value]) json.Value {
return json.from("$" + value.float.to_string(2))
})
let text = views.render_content("{{ name | upper }}: {{ price | money }}", Item { name: "Pen", price: 2.5 }) ! return
println(text) // PEN: $2.50
}
Templates in files#
Keep templates in a directory and embed them in the executable with views.set_many(#embed_dir("views")). The names are the paths relative to that directory, such as "pages/home.html". Changing a file then needs a rebuild. See Embedding files.
An engine belongs to one thread. In a server with several worker threads, make it a global with an initializer, which runs once per thread:
use valk.template
use valk.http
class Page {
title: String
}
global views: template.Engine (load_views())
fn load_views() template.Engine {
let engine = template.Engine.new()
engine.set("index.html", "<h1>{{ title }}</h1>") // or set_many(#embed_dir("views"))
return engine
}
fn handle(req: http.Request) http.Response {
let html = views.render("index.html", Page { title: "Home" }) ! return http.Response.text("Render failed", 500)
return http.Response.html(html)
}
fn main() {
http.serve("127.0.0.1", 9000, handle) ! return
}
Escaping#
{{ }} escapes for HTML; {! !} prints raw output, only for HTML your program made and trusts. Pass user content as data and never build template text from it.
Templates are not tied to HTML: set views.escape = null for plain text such as an e-mail body, or to your own function for another format. Template errors only show up when a template renders, not at compile time.

