AF-EfanUser Guide

Overview

afEfan is a Fantom library for rendering Embedded Fantom (efan) templates.

Much like EJS for Javascript, ERB for Ruby and JSP for Java, efan lets you embed snippets of Fantom code inside textual templates.

Efan aims to hit the middle ground between programmatically rendering markup with web::WebOutStream and rendering logicless templates such as Mustache.

Quick Start

xmas.efan:

<% ctx.times |i| { %>
  Ho!
<% } %>
Merry Christmas!

Fantom code:

template := `xmas.efan`.toFile.readAllStr

Efan().renderFromFile(template, 3)   // --> Ho! Ho! Ho! Merry Christmas!

Tags

Efan supports the following tags:

Eval Tags

Any tag with the prefix <%= will evaluate the fantom expression and write it out as a Str.

Hello, <%= "Emma".upper %>!

Comment Tags

Any tag with the prefix <%# is a comment and will be left out of the resulting template.

<%# This is just a comment %>

Fantom Code Tags

Any tag with the prefix <% will be converted into Fantom code.

<% echo("Hello!") %>

Passing Data

Tags are nice, but not much use unless you can pass data in. Thankfully, you can!

Each render method takes an argument called ctx which you can reference in your template. ctx is typed to whatever Obj you pass in, so you don't need to cast it. Examples:

Using maps:

ctx      := ["name":"Emma"]

template := "Hello <%= ctx["name"] %>!"

Efan().renderFromStr(template, ctx)

Using objs:

class Entity {
  Str name
  new make(Str name) { this.name = name }
}

...

ctx      := Entity("Emma")

template := "Hello <%= ctx.name %>!"

Efan().renderFromStr(template, ctx)

View Helpers

Efan lets you provide view helpers for common tasks. View helpers are mixins that your efan template can extend, giving your templates access to commonly used methods. Example, for escaping XML:

mixin XmlViewHelper {
  Str xml(Str str) {
    str.toXml()
  }
}

Set view helpers when calling efan:

Efan().renderFromStr(template, ctx, [XmlViewHelper#])

Template usage would then be:

<p>
  Hello <%= xml(ctx.name) %>!
</p>

Renderers

Efan works by dynamically generating Fantom source code and compiling it into a Fantom type. Because types can not be unloaded, if you were compile 1000s of efan templates, it could be considered a memory leak.

Each invocation of EfanCompiler.compile creates a new Fantom type, so use it judiciously. Caching the returned EfanRenderer types is highly recommended. Example:

template := "<% ctx.times |i| { %>Ho! <% } %>"
renderer := EfanCompiler().compile(template, Int#)	// <-- cache this type!

ho       := renderer.make.render(1)
hoho     := renderer.make.render(2)
hohoho   := renderer.make.render(3)

Layout Pattern / Nesting Templates

Efan templates may be nested inside one another, effectively allowing you to componentise your templates. This is accomplished by 2 in EfanRenderer:

** Renders the given efan renderer.
Void renderEfan(EfanRenderer renderer, Obj? ctx)

** Renders the body of the outer template.
Void renderBody()

This is best explained in an example. Here we will use the layout pattern to place some common HTML into a layout.efan file:

layout.efan:

<head>
  <title><%= ctx.pageTitle %></title>
</head>
<body>
  <div> header </div>
  <div> menu stuff </div>

  <% renderBody() %>

  <div> footer </div>
</body>

index.efan:

<html>
<% renderEfan(ctx.layout, ctx) { %>
  <div>
    ...my cool page content...
  </div>
<% } %>
</html>

Note that the layout component wraps content in index.efan which is rendered by the call to renderBody.

Index.fan:

using afEfan

class IndexCtx {
  EfanRenderer layout
  Str pageTitle
}

class Index {
  Str renderIndex() {
    compiler  := EfanCompiler()

    index     := compiler.compile(`index.efan`,  `index.efan`.toFile.readAllStr,  IndexCtx#).make
    layout    := compiler.compile(`layout.efan`, `layout.efan`.toFile.readAllStr, IndexCtx#).make
    ctx       := IndexCtx() { it.layout = layout; it.pageTitle = "Wotever" }

    return index.render(ctx)
  }
}

Running the above would then produce an amalgamation of the two templates:

<html>
<head>
  <title>Wotever</title>
</head>
<body>
  <div> header </div>
  <div> menu stuff </div>

  <div>
    ...my cool page content...
  </div>

  <div> footer </div>
</body>
</html>

Err Reporting

Efan compilation errs return snippets of code showing which line in the efan src the error occurred. Example:

Efan Compilation Err:
  file:/projects/fantom/Efan/test/app/compilationErr.efan : Line 17
    - Unknown variable 'dude'

    12: Five space-worthy orbiters were built; two were destroyed in mission accidents. The Space...
    13: </textarea><br/>
    14:         <input id="submitButton" type="button" value="Submit">
    15:     </form>
    16:
==> 17: <% dude %>
    18: <script type="text/javascript">
    19:     <%# the host domain where the scanner is located %>
    20:
    21:     var plagueHost = "http://fan.home.com:8069";
    22:     console.debug(plagueHost);

Release Notes

v1.1.0

  • New: Added EfanRenderCtx to ease efan extensions.
  • Chg: Updated to use afPlastic.
  • Chg: The EfanCompiler now returns the rendering type, not an EfanRenderer instance.
  • Chg: EfanRenderer is now a mixin and is implemented by the rendering type.
  • Chg: renderEfan() and renderBody() are now methods on EfanRenderer.

v1.0.0

  • New: Efan templates may now be nested and can optionally render their body!
  • Chg: EfanCompiler wraps the generated renderer in a sane const EfanRenderer wrapper.
  • Chg: Removed dependency on afBedSheet - see afBedSheetEfan for BedSheet integration.
  • Chg: Removed dependency on afIoc, all afPlastic has been copied in to efan.
  • Chg: Updated docs.

v0.0.4

  • New: Hooked error reporting into afBedSheet.
  • New: EfanErr now gives code snippets and line numbers of parsing and compilation errors.
  • Chg: Re-factored fantom code generation.

v0.0.2

  • New: Preview release.