Templates
Keep presentation separate from data by rendering templates at runtime.
On this page
valk.template renders named templates at runtime. Create an engine, register template contents, pass a typed data value to render, and handle a template error if rendering fails.
Render a small template#
This complete example keeps a template in a string:
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 the page")
return
}
println(html)
}
{{ title }} looks up the title field in the supplied data. By default, interpolated values are HTML-escaped, so the ampersand appears as & in the rendered markup.
Move markup into files#
For an application with several views, keep templates in a directory and register their contents with:
views.set_many(#embed_dir("views"))
Template names are the paths relative to that directory. Embedding bundles the files into the executable, so changing one requires a rebuild. Rendering still happens at runtime; embedding does not validate every template expression during compilation.
An engine belongs to one thread. In a server with several worker threads, declare it as a global with an initializer: every thread runs the initializer, so each worker gets its own engine.
global views: template.Engine (load_views())
fn load_views() template.Engine {
let engine = template.Engine.new()
engine.set_many(#embed_dir("views"))
return engine
}
Repeat and include#
A template can include another registered template and iterate over an array from its data:
@include("header.html")
<h1>{{ title }}</h1>
@each(notes as note)
<article>
<h2>{{ note.title }}</h2>
<p>{{ note.body }}</p>
</article>
@end
The supplied data should have a title and a notes array whose elements expose title and body. This keeps view structure in the template while application code decides which notes to render.
Conditions use @if(...), optional @elif(...) and @else, then @end; a condition can combine comparisons with &&, || and !. @empty before the @end of a loop renders when the list has nothing in it. A directive on a line of its own takes the whole line with it, so loops and conditions do not leave blank lines in the page.
Format a value with a filter#
A filter changes a value on its way into the page. {{ title | upper }} prints the title in capitals, and filters can take arguments and chain: {{ summary | truncate(80) | escape }}. The built-in filters cover text (upper, lower, capitalize, trim, truncate, replace), lists (join, first, last, length), numbers (round) and a default("...") for empty values. Register your own on the engine with set_filter:
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 html = views.render_content("{{ name | upper }}: {{ price | money }}", Item { name: "Pen", price: 2.5 }) ! {
println("Could not render")
return
}
println(html) // PEN: $2.50
}
Keep escaping intentional#
{{ value }} escapes its output. {! value !} inserts raw output, which is useful for HTML your application has already rendered and trusts. Raw insertion does not make arbitrary input safe to place in a page.
Templates are not only for HTML. The engine's escape property holds the function applied to {{ }} output; set it to null for plain text such as an e-mail body, or to your own function for another format.
Treat template files as application code and pass user content as data. Do not concatenate user content into a new template source string.
Return the rendered page#
An HTTP handler can pass a successfully rendered string to http.Response.html(html). Decide how the handler should respond if rendering fails, rather than returning partial markup. See HTTP & networking for the handler structure.