Skip to content

"Semi" logic-less templates for Fantom

License

Notifications You must be signed in to change notification settings

novant-io/fanbars

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fanbars

"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.

Installation

Install into your current Fantom repo using fanr -- full API docs over on Eggbox:

$ fanr install fanbars

Usage

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

Syntax

Variables

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&amp;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

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.

Elvis Operator

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 Iterator Blocks

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

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

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

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

Comments use the {{!-- text --}} sytnax. Comments are omitted from the rendered output:

template:
  {{!-- this is a comment --}}
  Hello, World

output:
   Hello, World