View
Template rendering and named view domains
view manages named template domains. It provides a standard-library html/template renderer by default and supports local directories, embedded filesystems, development reload, and template functions.
Install
go get github.com/duxweb/runa/view
Connect to an application
package main
import (
"context"
"github.com/duxweb/runa"
"github.com/duxweb/runa/provider"
"github.com/duxweb/runa/route"
"github.com/duxweb/runa/view"
)
type appModule struct {
provider.ModuleBase
}
func (appModule) Name() string { return "app" }
func (appModule) Register(ctx context.Context, app provider.Context) error {
views, err := provider.Invoke[*view.Registry](app)
if err != nil {
return err
}
return views.Register(ctx, "web", view.HTML(view.Dir("views").Reload(true)))
}
func main() {
app := runa.New()
app.Install(route.Provider(route.Addr(":8080")), view.Provider())
app.Module(appModule{})
route.Default().Get("/", func(ctx *route.Context) error {
return ctx.Render("index", runa.Map{"title": "Runa"}, "web")
})
if err := app.Run(context.Background()); err != nil {
panic(err)
}
}
Standalone New usage
registry := view.New()
_ = registry.Register(context.Background(), "web", view.HTML(view.Dir("views")))
html, err := registry.RenderString(routeContext, "web", "index", map[string]any{"title": "Runa"})
_ = html
_ = err
Render requires a context that implements Context() context.Context. In HTTP usage, use *route.Context directly.
Config
view currently has no TOML config. Template sources and functions are usually registered in code, making it easy to switch between local directories and embedded filesystems by environment.
RHTML enhanced templates
Default view.HTML(...) uses the standard library html/template. If you need layouts, blocks, includes, conditions, loops, and custom r: tags, use view/rhtml.
import "github.com/duxweb/runa/view/rhtml"
renderer := rhtml.New(view.Dir("views", "**/*.html").Reload(true))
_ = views.Register(context.Background(), "web", renderer)
Full syntax is documented in RHTML Templates.
Template sources
view.Dir("views", "**/*.html").Reload(true)
view.Embed(viewFS, "views", "**/*.html")
view.Embed(viewFS, "views").Dev(view.Dir("views").Reload(true))
Template functions
Static helpers can be registered on a concrete renderer:
renderer := view.HTML(view.Dir("views")).Func("asset", func(path string) string {
return "/assets/" + path
})
_ = views.Register(context.Background(), "web", renderer)
You can also register helpers on view.Registry, so every later renderer gets the same functions:
views.Func("asset", func(path string) string {
return "/assets/" + path
})
Use ContextFunc for request-scoped helpers. It exposes the function name while parsing templates, then builds the real function from context.Context on every render:
views.ContextFunc("tenant", func(ctx context.Context) any {
return func() string {
return tenantFromContext(ctx)
}
})
Both view.HTML and view/rhtml use this same Func / ContextFunc mechanism. The translation helper {{ t "key" }} is injected by lang/view.Provider() through ContextFunc.
Common mistakes
Installing view.Provider but not registering a renderer
view.Provider() registers the view registry. You still need to register at least one renderer, such as an RHTML renderer.
ctx.Render reports renderer is not configured
Check that the renderer name passed to ctx.Render(..., name) matches the name registered in view.Registry.Register(...).
Template changes do not appear in development
For local template files, enable reload with view.Dir("views", "**/*.html").Reload(true) during development.
API quick reference
view.New()creates a standalone registry.view.Provider()connects to the framework lifecycle.view.Default()reads*view.Registryfrom default DI.registry.Register(ctx, name, renderer)registers a view domain.registry.Render(ctx, writer, domain, name, data)renders to a writer.registry.RenderString(ctx, domain, name, data)renders to a string.view.HTML(sources...)creates an html/template renderer.view.Dir,view.Embedcreate template sources.