The Haxe Template System

Haxe comes up with a standard Template System with an easy to use syntax which is interpreted by a lightweight class called haxe.Template.

A Template is a string or a file that is used to produce some HTML or XML output depending on the input. Here's a small template example :

My name is <strong>::name::</strong> and I'm <em>::age::</em> years old.

Let's save this in a file named sample.mtt. You can use it from Haxe by using the following code :

class App {
    static function main() {
        var str = haxe.Resource.getString("my_sample");
        var t = new haxe.Template(str);
        var output = t.execute({ name : "John", age : 33 });
        trace(output);
    }
}

You can compile this example, and include the sample.mtt by using the following HXML :

# enable the platform you want by removing the sharp :
#-swf app.swf
#-neko app.n
#-js app.js
-resource sample.mtt@my_sample
-main App

This should display the string where the variables have been replaced by the values passed at runtime in the context.

Expressions

Between the ::, you can put some expression. The syntax is currently restricted to the current possibilities :

  • ::name:: : the variable name
  • ::(135):: : the integer 135. Float constants are not allowed
  • ::(expr):: : the expression expr is evaluated
  • ::(e1 op e2):: : the operation op is applied to e1 and e2
  • ::expr.field:: : field access

The syntax is a bit restrictive, since it doesn't deal with operator precedence, so you need to put parenthesis around each operator : for example ::((1 + 3) == (2 + 2)):: will display true. There are no calls either.

If/Else/Elseif

You can add some tests in the template :

::if flag:: OK ! ::end::
::if flag:: OK ! ::else:: FAILED ! ::end::
::if flag1:: OK ! ::elseif flag2:: MAYBE ::else:: NO ::end::

Foreach

You can iter on a structure (an Iterator or an Iterable Data structure) by using foreach :

<table>
<tr><td>Name :</td><td>Age :</td></tr>
::foreach users::<tr><td>::name::</td><td>::age::</td></tr>::end::
</table>

You can access the current context value of the foreach using the special variable __current__, especially useful when iterating over nested containers:

::foreach rows::
    ::foreach __current__::
    ::end::
::end::

A Complete Sample

Here's a Template using several aspects of the syntax :

The habitants of <em>::name::</em> are :
<ul>
::foreach users::
  <li>
    ::name:: 
    ::if (age > 18)::Grown-up::elseif (age <= 2)::Baby::else::Young::end::
  </li>
::end::
</ul>

And here's the class filling the Data :

class User {
    var name : String;
    var age : Int;
    public function new(name,age) {
        this.name = name;
        this.age = age;    
    }
}

class Town {
    var name : String;
    var users : Array<User>;
    public function new( name ) {
        this.name = name;
        users = new Array();
    }
    public function addUser(u) {
        users.push(u);
    }
}

class App {
    static function main() {
        // build the data model
        var town = new Town("Paris");
        town.addUser( new User("Marcel",88) );
        town.addUser( new User("Julie",15) );
        town.addUser( new User("Akambo",2) );
        // display it
        var str = haxe.Resource.getString("my_sample");
        var t = new haxe.Template(str);
        var output = t.execute(town);
        // if on the server, don't add file/line infos
        #if neko
        neko.Lib.print(output);
        #else true
        trace(output);
        #end
    }
}

When running the template system on the server side, you can simply use neko.Lib.print instead of trace to display the HTML template to the user.

Sub-templates

Often users want to be able to include templates inside other templates. To do that, simply pass the sub-template result string as a parameter :

   var t1 = new haxe.Template("My sub template is ::sub::");
   var t2 = new haxe.Template("::foreach items:: (::i::) ::end::");
   var t2str = t2.execute({ items : [{ i : 0 },{ i : 33 },{ i : -5 }] });
   var t1str = t1.execute({ sub : t2str });
   trace(t1str);

Globals

You can use the Template.globals object to store those values that should be applied to all templates.

Macros

If you want to be able to callback your code when some parts of the template are rendered, you can use Macros for that. Here's a small example :

class App {

    // a macro returns a string
    static function myfun( resolve : String -> Dynamic, title : String, p : Int ) {        
        return "["+title+"="+(p * resolve("mult"))+"]";
    }

    static function main() {
        var t1 = new haxe.Template("Call macro : $$myfun(Hello,::param::)");
        var str = t1.execute({ param : 55, mult : 2 },{ myfun : myfun });
        trace(str); //output: "Call macro : [Hello=110]"
    }
}

A macro is called with the following parameters :

  • the resolve function, which can be called to retreive some value from the Template context.
  • all the parameters passed in the Template. If this parameter is a context variable ::v:: (without spaces around !) then it is passed to the macro without any string conversion. If it's a more complex expression, it's evaluated first, then passed to the macro.

version #13761, modified 2012-04-15 08:48:47 by elyon