"Semi" logic-less templates for Fantom inspired by Mustache.
Fanbars can be used for HTML, config files, source code - anything. It works
by expanding tags in a template using values provided in a Map
.
{{!-- Show the current cart contents --}}
{{#if isLoggedIn}}
Hello, {{username}}!
<ul>
{{#each item in cart}}
<li>{{item.name}} ${{item.price}}</li>
{{/each}}
</ul>
{{/if}}
We call it "semi" logic-less because there are some basic control structures
for if/else
and each
blocks. Technically Mustache has these as well, but
its a bit opaque (and confusing) how they get exposed. So Fanbars goes ahead
and makes them explicit. This seemed like a good middle ground for what you
need in real projects without adding any "real" logic to templates.
Install into your current Fantom repo using fanr
-- full API docs over on
Eggbox:
$ fanr install fanbars
Render text by compiling your template then renderering:
Fanbars.compile(template).render(out, map)
The input template can be an InStream
, Str
or File
instance:
Fanbars.compile(in)
Fanbars.compile(file)
Fanbars.compile("Hello {{name}}!")
Output can be streamed to an OutStream
instance, or return a fully rendered
Str
:
f := Fanbars.compile(template)
s := f.renderStr(map) // return as Str
f.render(out, map) // stream to out
Variable substitution uses the {{var}}
syntax:
template:
Hello {{name}}!
map:
["name":"Bob"]
output:
Hello Bob!
By default all variable text is HTML escaped using
OutStream.writeXml
. To
replace text unescaped use the "triple-stash" {{{
:
template:
This is {{foo}} and this is {{{foo}}}
map:
["foo":"A&W"]
output:
This is A&W and this is A&W
Method chaining is supported for Obj
variables:
template:
The last day of the month is {{date.lastOfMonth.toLocale}}
map:
["data":Date.today]
output:
The last day of the month is 31-Jan-2022
If blocks use the {{#if var}}
syntax, where the value of var
is considered
false
if its null
or false
, everything else is true
:
template:
{{#if isLoggedIn}}
Hello {{name}}!
{{/if}}
map:
["isLoggedIn":true, "name":"Bob"]
output:
Hello Bob!
Use #ifnot
to check the inverse of var
:
template:
{{#ifnot isLoggedIn}}
Not logged in!
{{/ifnot}}
map:
["isLoggedIn":false]
output:
Not logged in!
The {{#if}}
block supports an optional is
or isnot
argument that can be
used to perform a string comparison during evaluation:
template:
{{#if alpha is "active"}}Alpha is active!{{/if}}
{{#if beta isnot "active"}}Sorry, Beta is not currently active.{{/ifnot}}
map:
["alpha":"active", "beta":"inactive"]
output:
Alpha is active!
Sorry, Beta is not currently active.
The elivis operator ?:
can be used to inline an if-else
statement, where if
the value is null
then the supplied literal is placed instead:
template:
Case A: {{foo ?: "(none)"}}
Case B: {{bar ?: "(none)"}}
map:
["foo":125]
output:
Case A: 125
Case B: (none)
Each blocks use the {{#each v in var}}
syntax, where the value of v
is
replaced with each element in var
:
template:
{{#each v in items}}
Item #{{v}}
{{/if}}
map:
["items":[1,2,3]]
output:
Item #1
Item #2
Item #3
Generators use the {{#gen var}}
syntax, where var
is a Fantom function that
dynamically generates content when the template is rendered at runtime:
template:
{{#gen foo}}
map:
["foo": |OutStream out| { out.print("generated") }]
output:
generated
Helpers invoke a Fantom method to produce content based on one or more vars:
template:
{{#helper acme::Bar.helper foo}}
map:
["foo":"Bob"]
acme::Bar.helper:
Str helper(Obj? val) { "Hello there ${val}!" }
output:
Hello there Bob!
Helper funcs may take multiple arguments, where each arg may be a var
def
or a string literal:
template:
{{#helper acme::Bar.helper foo bar "some string"}}
Partials use the {{#partial var}}
syntax to inject content from another
template. The partial is rendered at runtime. Partials must be compiled ahead
of time and passed to render()
:
Fantom:
p := ["welcome": Fanbars.compile(welcome)]
s := Fanbars.compile(template).renderStr(map, p)
welcome:
Welcome, {{user}}!
template:
{{#partial welcome}}
Enjoy your stay.
map:
["user": "Andy"]
output:
Welcome, Andy!
Enjoy your stay
Comments use the {{!-- text --}}
sytnax. Comments are omitted from the
rendered output:
template:
{{!-- this is a comment --}}
Hello, World
output:
Hello, World