Render context
HAML.render accepts a :context named argument — a Raku object whose methods
become reachable inside templates as bare identifiers in expression
position. This is the template's "view context": a place to put template-facing
helpers without polluting global scope.
class MyView {
has Str $.title;
method greeting { 'world' }
}
HAML.render(
:src(q:to/HAML/),
%h1= title
%p= greeting
HAML
:context(MyView.new(:title('Hello'))),
);
renders:
<h1>Hello</h1>
<p>world</p>
Bare identifiers#
A "bare identifier" is an expression that is exactly one name — letters, digits, underscore, hyphen — with no parentheses, sigils, or operators. Examples:
| Expression | Bare identifier? |
|---|---|
title |
yes |
user-count |
yes |
$title |
no — has sigil |
title() |
no — has parens |
title.uc |
no — has . |
A bare identifier in =, !=, &=, ~, or in a control-flow condition
(if, unless, elsif, while, repeat, given) resolves through:
- Locals — if
%locals{name}exists, use it. - Context method — otherwise if
context.^can(name)is true, callcontext.name. - Fallback to eval — otherwise the expression is handed to the normal
Raku evaluator. If the name isn't a valid Raku identifier in scope, you'll
get
X::HAML::Eval.
Context methods with arguments#
A context method is also callable as a bare name with arguments, so a helper that takes parameters reads like a plain function call, no sigil:
class AppView does Template::HAML::HelpersRole {
method link-to($text, $href) { qq{<a href="$href">$text</a>} }
}
%nav
!= link-to('Home', '/')
!= link-to(label, url)
Each context method named in an expression is bound as a lexical routine for
that render, dispatching to the context. Locals are still referenced with their
sigil ($label), since only standalone bare identifiers resolve to a local.
Built-in helpers (surround, list-of, …) remain callable as before. This
works in the interpreted renderer and the default direct-emit renderer.
- if is-admin
%p= admin-greeting
- else
%p= public-greeting
Overriding a built-in helper#
A context method takes precedence over the built-in helper of the same name. To
replace surround, partial, list-of, or any other built-in for a given
render, define a method with that name on your context:
class AppView does Template::HAML::HelpersRole {
method surround($pre, $post, &block) { "{$pre}OVERRIDDEN{$post}" }
}
%div
!= surround('[', ']', { 'inner' })
renders <div>[OVERRIDDEN]</div> instead of the built-in [inner]. Because the
default ViewContext exposes the built-ins as methods, every built-in name is
resolved through the context, so overriding one is just defining it.
Declaring helpers explicitly#
Helper discovery normally introspects the context with .^methods. Helpers
installed in a way that introspection does not report, such as methods added at
runtime with ^add_method or handled through FALLBACK, are invisible to that
scan. Declare them with a haml-helper-names method that returns their names:
class DynamicView does Template::HAML::HelpersRole {
method haml-helper-names { <ping pong> }
method FALLBACK($name, |args) { ... }
}
When haml-helper-names is present its list is trusted verbatim: each entry is
kept if it is a valid helper name (starts with a lowercase letter or underscore,
then word characters or hyphens), then de-duplicated and sorted. The list is not
cached, so a context whose helper set varies per instance can return a different
list each time.
Helpers and the compiled cache#
A context's helper names take part in the compiled-template cache key used by
render-cached and render-file-cached. The compiled module is specialized to
the helper set, so the same source rendered against two contexts with different
helpers compiles and caches separately, and a helper is never resolved against a
module compiled for a context that lacked it. See
On-disk compiled-template cache.
Default context#
If you don't pass :context, the renderer constructs a
Template::HAML::ViewContext for you. It composes the
Template::HAML::HelpersRole role, so every built-in helper (surround,
escape-once, list-of, find-and-preserve, html-safe, capture-haml,
yield, content-for, tab-up, tab-down, haml-concat) is reachable as
a method on the context.
Extending the context#
Two composition points are supported.
Mix in the helper role into your own class:
use Template::HAML::HelpersRole;
class AppContext does Template::HAML::HelpersRole {
has $.user;
method current-user-name { $!user.name }
}
HAML.render(:src($src), :context(AppContext.new(:$user)));
The role brings the built-in helpers along, so you can add your own methods
without losing surround, list-of, etc.
Subclass ViewContext:
use Template::HAML::ViewContext;
class AppContext is Template::HAML::ViewContext {
method site-name { 'MySite' }
}
Either approach works; the role form is slightly more flexible because you can compose other roles alongside it:
role Greeter { method hello { 'hi' } }
role Footer { method footer { 'fineprint' } }
class AppContext does Template::HAML::HelpersRole does Greeter does Footer { }
Bare identifiers (hello, footer, surround, …) resolve against every
composed role.
Locals still win#
When both a local and a context method have the same name, the local wins:
class MyView {
method title { 'from-context' }
}
HAML.render(
:src("%h1= title\n"),
:context(MyView.new),
:locals(%(title => 'from-locals')),
);
# → <h1>from-locals</h1>
This precedence keeps per-render values from being silently shadowed by a stray helper method.