context
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 "view context" of frameworks like Rails: 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-greetingOverriding 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.
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 is the same precedence used by Rails-style view contexts and keeps per-render values from being silently shadowed by a stray helper method.