Docs/Building applications

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:

Valk
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 &amp; in the rendered markup.

Move markup into files#

For an application with several views, keep templates in a directory and register their contents with:

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

Valk
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:

HTML
@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:

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