Helma Logo
main list history
previous version  overview  next version

Version 2 by Lehni on 05. January 2006, 08:22

A short summary of my efforts for a more flexible template engine:

First there was my trial of creating a Velocity Engine Extension for Helma: <a href="http://helma.org/pipermail/helma-dev/2005-November/002358.html">23-11-05</a>. I soon noticed that between FreeMarker and Velocity, FreeMarker seems the more powerfull of the two, and implemented a second extension for this templating engine.
There is a text on my website about it: <a href="http://www.scratchdisk.com/Random+Notes/November+2005/Helma+Templates/">Helma Template Engines</a>.

The two extensions are available through CVS: <a href="http://adele.helma.org/source/viewcvs.cgi/extensions/freemarker/?cvsroot=hop">FreeMarker Extension</a>, <a href="http://adele.helma.org/source/viewcvs.cgi/extensions/velocity/?cvsroot=hop">Velocity Extension</a>.

And here are the readme files with a brief description of how to install and use the extensions: <a href="http://adele.helma.org/source/viewcvs.cgi/extensions/freemarker/README.txt?rev=HEAD&cvsroot=hop&content-type=text/plain">FreeMarker README.txt</a>, <a href="http://adele.helma.org/source/viewcvs.cgi/extensions/velocity/README.txt?rev=HEAD&cvsroot=hop&content-type=text/plain">Velocity README.txt</a>.

The FreeMarker extension is able to call Helma macros through a slightly different syntax: <tt>&lt;@this.macro foo="bar" bla="bla" /&gt;</tt>. As an extension, it also is possible to use nested content in macros: <tt>&lt;@this.nested foo="bar"&gt;Nested text&lt;/@&gt;</tt>. The nested contend is passed to the macro as a second argument.

First I was very convinced by FreeMarker and though it was a nice and fitting extension to Helma. But some things I thought were disturbing: It was relatively slow compared to Helma skins, and the builtin modifiers for various datatypes felt very different to Helma's own approach, e.g. format for date types and numbers, and not very JavaScript-like.

There was an unnecessary redundancy between the Rhino parser for JavaScript and FreeMarker's own parser and rendering/runtime engine for templates, which does not seem to be very optimized. On top of that, Rhino's JavaScript values, objects and functions needed wrapper objects for both the Velocity and FreeMarker engine to be able to access from the templates, which was causing additional consumption of resources and slowdowns and felt unnecessary.

The resolution seemed simple: Why not using Rhino as a template engine by parsing templates and turning them into JavaScript code, which then is evaluated and cached as a function object inside the template object. I then put a simple prototype together which worked surprisingly well and performed almost as good as Helma skins, although written in JavaScript.

I am using this third template enigne now in one of my projects, adding and modifying it when I encounter problems or needs for more functionality. It is by no mean finished, but I provide it here anyway because I think it can serve as a nice starting point for quick sketching and trying out of new template approaches for Helma 2.

==JavaScript Tempalte Engine==

The implementation is based on my comments in <a href="http://helma.org/pipermail/helma-dev/2005-December/002461.html">06-12-05</a>. As far as I know, the implementation is fully backward compatible with Helma 1 skins and macros.

<a href="http://dev.helma.org/static/helma.dev/Wiki/Helma%202%20Templates%20-%20juerg/Template.js">Download</a>

The file needs to be placed in the global protoype, it adds one function to the HopObject prototype:

<tt>HopObject.renderTemplate(name, param, out)</tt>

* name is the name of the skin, without the extension. "jstl" is used as extension for the template files, which stands for "JavaScript Template Language".
* param is the optional parameter, which can be accessed from the templates, just like in Helma skins.
* out is the writer object where the whole template is written to. if none is specified, renderTemplate renders into a StringBuffer and returns its value as a string.

====Overview of Statements

* <tt>&lt;% foreach (object in list) %&gt; ... &lt;% end %&gt;</tt>: loops over the list, on purpose not named "for (object in list)", because object won't be the index but the object itself
* <tt>&lt;% if () %&gt;, &lt;% elseif () %&gt;, &lt;% else %&gt;, &lt;% end %&gt;</tt>
* <tt>&lt;% set variable = otherVariable [ + some arithmetic ] %&gt;,</tt> e.g. <tt>&lt;% set count = this.nodes.count() %&gt;</tt>
* <tt>&lt;% macro foo="bar" array=[1,2,3] hash={a:1, b: 2, c:3} %&gt;</tt>: calls the macro, both global macros and object based macros are supported (e.g. this.macro, root.macro, etc.). prefix, suffix, default are supported. It is supposed to be fully backward compatible with Helma 1 skins.
* <tt>&lt;% macro &gt;nested content&lt;/%&gt;</tt>: passes the nested content to the macro, as a second parameter.
* <tt>&lt;% param.value %&gt;</tt>: prints out the value, response, request, sesion, param are suppored, as links to res.data, req.data, session.data, param. This is here for backward compatibility. The prefered way of printing data is:
* <tt>&lt;%= param.object.href() %&gt;</tt>: prints out the value or the result of a function call. response, request, session do NOT link res.data, req.data, session.data.

====Examples

Conditional statements, expressions:

  <% if (session.user != null) %>
    Hello <%= session.user.name %>
  <% else %>
    <a href="/login">Login</a>
  <% end %>

Loops:

  <table>
  <% foreach (topic in param.topics) %>
    <tr class="topic">
      <td><%= topic.renderLink() %></td>
      <td><%= topic.creator.renderLink() %></td>
      <td>
      <% set count = topic.comments.count() - 1 %>
      <% if (count == 1) %>
        1 Post
      <% elseif (count > 1) %>
        <%= count %> Posts
      <% else %>
        &nbsp;
      <% end %>
      </td>
      <td nowrap><%= topic.createDate.format("dd.MM.yy - HH:mm") %></td>
    </tr>
  <% end %>
  </table>

     removed
     added