Skip to content

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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:

1
2
<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:

  1. Locals — if %locals{name} exists, use it.
  2. Context method — otherwise if context.^can(name) is true, call context.name.
  3. 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.
1
2
3
4
- if is-admin
  %p= admin-greeting
- else
  %p= public-greeting

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:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
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:

1
2
3
4
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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.