AF-EfanUser Guide

Overview

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

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

Efan hopes to hit the middle ground between programmatically rendering markup with web::WebOutStream and using logicless templates with Mustache.

Quick Start

xmas.efan:

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

Fantom code:

@Inject EfanTemplates efanTemplates

...

// --> Ho! Ho! Ho! Merry Christmas!
txt := efanTemplates.renderFromFile(`xmas.efan`.toFile, 3)

Usage

To use afEfan just add it as dependency in your project's build.fan. Example:

depends = ["sys 1.0", "afIoc 1.4+", "afBedSheet 1.0+", "afEfan 0.0.4+", ... ]

...and that's it!

Because afEfan defines pod meta-data for afIoc, no more configuration or setup is required.

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" %>!

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:

Use maps:

template := "Hello <%= ctx["name"] %>!"
efanTemplates.renderFromStr(template, ["name":"Emma"])

Use objs:

template := "Hello <%= ctx.name %>!"
efanTemplates.renderFromStr(template, Entity() { it.name = "Emma"})

...

class Entity {
  Str? name
}

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:

const mixin XmlViewHelper {
  Str x(Str str) {
    str.toXml()
  }
}

Contribute View Helpers in your AppModule:

@Contribute { serviceType=EfanViewHelpers# }
static Void contrib(OrderedConfig conf) {
  conf.add(XmlViewHelper#)
}

Template usage would then be:

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

Err Reporting

afEfan automatically hooks into afBedSheet to give detailed reports on efan compilation errors. The reports are added to the default afBedSheet Err500 page.

Efan Compilation Err:
  file:/C:/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);

Fair Usage Policy!

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.

For this reason, templates compiled from files are cached and reused, therefore posing no risk in your typical web application(*). But templates compiled from Strs are not cached and so calls to this method should be made judiciously.

(*) If a template file seen to be modified, the efan template is re-compiled on the fly. This is fine in dev, but be weary of modifying template files in live.

Non afIoc Applications

Efan was created predominantly for afIoc applications but may still be used outside of an IoC by means of the EfanCompiler class. Example:

template := "<% ctx.times |i| { %>Ho! <% } %>Merry Christmas!"
output   := EfanCompiler().compile(template, Int#)->render(3)

Note that each invocation of EfanCompiler.compile creates a new Fantom type. So it is suggested that it be called sparingly as not to cause a memory leaks. Caching of the returned renderers is highly recommended.

The Obj returned from compile has the following slots:

Type ctxType
Str render(<ctxType> ctx)

where ctxType is the type passed into the compile method. Because the render() method signature is dependant on the ctx type, the return value can not be a static mixin type. Therefore all renderer invocations must be dynamic.

Release Notes

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.