pod afEfan

A library for rendering Embedded Fantom (efan) templates

Mixins

EfanTemplate

A compiled efan template, ready for rendering!

Classes

Efan

Methods for compiling and rendering efan templates.

EfanTemplateMeta

Meta data about an efan template.

Errs

EfanErr

As thrown by Efan.

Overview

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

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.

ALIEN-AID: Create powerful re-usable components with efanXtra and IoC !!!

ALIEN-AID: If rendering HTML, use Slim !!! The concise and lightweight template syntax makes generating HTML easy!

Install

Install efan with the Fantom Repository Manager ( fanr ):

C:\> fanr install -r http://repo.status302.com/fanr/ afEfan

To use in a Fantom project, add a dependency to build.fan:

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

Documentation

Full API & fandocs are available on the Status302 repository.

Quick Start

1). Create a text file called xmas.efan:

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

2). Create a text file called Example.fan:

using afEfan

class Example {
    Void main() {
        template := `xmas.efan`.toFile.readAllStr

        text := Efan().renderFromFile(template, 3)  // --> Ho! Ho! Ho! Merry Christmas!
        echo(text)
    }
}

3). Run Example.fan as a Fantom script from the command line:

C:\> fan Example.fan
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 %>

Code Tags

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

<% echo("Hello!") %>

Instruction Tags

The content of any tag with the prefix <%? is taken to be a Fantom using instruction.

<%? using concurrent::Actor %>

Escaping Tags

All efan tags can be escaped by adding an extra % character to the start and end tags. Example:

This is how you <%%= escape %%> efan tags.

prints:

This is how you <%= escape %> efan tags.

Whitespace

All whitespace in efan templates is preserved, except for when a line exists only to contain a code block (or similar). This has the effect of removing unwanted line breaks. Consider:

Hey! It's
<% if (ctx.isXmas) { %>
  Christmas
<% } %>
Time!

would be rendered as

Hey! It's
  Christmas
Time!

and not:

Hey! It's

  Christmas

Time!

(Advanced users may turn this feature off in EfanCompiler.)

Template Context

Each template 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"]  // ctx is a map

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

Efan().renderFromStr(template, ctx)

Using objs:

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

...

template := "Hello <%= ctx.name %>!"
ctx      := Entity("Emma")  // ctx is an Entity

Efan().renderFromStr(template, ctx)

ALIEN-AID: All classes not in sys either need to be imported with <%? using %> statements or referenced by their fully qualified class name (FQCN), example:

<% concurrent::Actor.sleep(2sec) %>

<%? using concurrent %><% Actor.sleep(2sec) %>

This includes classes in your application too!

View Helpers

Efan lets you provide view helpers for common tasks. View helpers are mixins that your efan template will 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>

Layout Pattern / Nesting Templates

Efan templates may be nested inside one another, effectively allowing you to componentise your templates. This is accomplished by passing body functions in to the efan render() method and calling renderBody() to invoke it.

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 %></title>
</head>
<body>
  <%= renderBody() %>
</body>

index.efan:

<html>
<%= ctx.layout.render("Cranberry Whips") { %>
    ...my cool page content...
<% } %>
</html>

Code to run the above example:

Index.fan:

using afEfan

class Index {
  Str renderIndex() {
    index     := efan().compileFromFile(`index.efan` .toFile, EfanTemplate#)
    layout    := efan().compileFromFile(`layout.efan`.toFile, Str#)

    return index.render(layout)
  }
}

This produces an amalgamation of the two templates:

<html>
<head>
  <title>Cranberry Whips</title>
</head>
<body>
    ...my cool page content...
</body>
</html>

Err Reporting

Efan compilation and runtime Errs report snippets of code showing which line in the efan template 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);

This really helps you see where typos occurred.

Templates

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 Efan.compileXXX() creates a new Fantom type, so use it judiciously. Caching the returned EfanTemplate classes is highly recommended. Example:

efanStr  := "<% ctx.times |i| { %>Ho! <% } %>"
template := Efan().compileFromStr(efanStr, Int#)  // <-- cache this template!

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

Release Notes

v1.4.2

  • Chg: EfanEngine generates less brittle rendering code.

v1.4.0

  • New: Intelligent whitespace removal.
  • Chg: Overhauled and simplified the public API, mainly the advanced classes. (Breaking change.)
  • Chg: Split EfanCompiler up to create EfanEngine, a good seperation of concerns. (Breaking change.)
  • Chg: Renamed EfanRenderer --> EfanTemplate. (Breaking change.)
  • Chg: Renamed EfanMetaData --> EfanTemplateMeta. (Breaking change.)

v1.3.8

  • Chg: Updated licence to The MIT Licence.

v1.3.6

  • New: using statements can be added with <%? using ... %> notation
  • New: efan tags can be escaped with <%% ... %%> notation.
  • Bug: Rendering null values with debug logging turned on could cause NPEs.

v1.3.4

  • Chg: Added withXtraMsg() to EfanErrs so Err msgs can be appended to.
  • Chg: Internal API changes for afEfanXtra.

v1.3.2

  • Chg: Moved EfanRenderer.id -> EfanMetaData.templateId
  • Bug: Non ASCII templates could not be compiled.

v1.3.0

  • Chg: Rejigged the public efan API.
  • Chg: Removed EfanRenderer.renderEfan(...). All template rendering is done via EfanRenderer.render(...).
  • Chg: Nested efan templates and body functions now return a Str, so you MUST use eval tags; <%= renderBody() %>
  • Chg: Massivly simplified nested component rendering by introducing a threaded EfanCtxStack.
  • Chg: Added EfanRenderer.id to make debugging efanXtra a bit more humane!

v1.2.0

  • New: Runtime Errs thrown while rendering report efan template code snippets and line numbers.
  • New: EfanRenderer now has an efanMetaData field with more contextual information.
  • New: The class name of efan renderer instances is now configurable.
  • Chg: Rejigged the efan parser.
  • Chg: Efan template line numbers no longer take up a whole line of code.
  • Chg: EfanCompiler now returns a const EfanRenderer instance, not a rendering type.
  • Chg: Exposed (made public) the EfanErr hierarchy.
  • Chg: Added optional makeFunc to EfanCompiler.compileWithModel().

v1.1.0

  • New: Added EfanRenderCtx to ease efan extensions.
  • Chg: Updated to use Plastic.
  • Chg: 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 BedSheet - see afBedSheetEfan for BedSheet integration.
  • Chg: Removed dependency on IoC, all Plastic code 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.