Helma Logo
main list history
previous version  overview  next version

Version 31 by Lehni on 08. November 2006, 02:19

Read *Helma 2 Templates - juerg* for a short overview of my efforts towards a more flexible templating engine in Helma (2).

The JavaScript Tempalte Engine is implemented in Helma 1.5, not Helma 2, but functions as a testcase for exploration of concepts for future releases. This doesn't stop me from using it in real websites as well, and so far it is working very well for me. It actually took away the urge to solve the problem.

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

*Download|http://dev.helma.org/static/helma.dev/Wiki/JavaScript%20Template%20Engine/Template.js*

Revisions:
  
0.18  Added support for encoding parameter, fixed a bug in <% else %>
 
0.19  Added support for sub templates and a special macro
      for both rendering sub templates and external templates.
 
0.17  Many clean-ups and simplifications of regular expressions and parsing.
      At parsing time, no code is evaluated any longer, except the final
      result. This leads to further speed improvements by about 1.5.
 
0.16  Fixed issues with the display of errors. The correct line numbers
      should be reported now. If a macro call results in an exception
      The exception is caught and repported just like in Helma skins now.
 
0.15  Added the possibility for macro tags to suppress the following line
      separator, if there is any, by adding a minus at the end: <% macro -%>.
      Control tags like if, else, elseif, end, foreach, set and comments
      (<%-- --%>) automatically suppress the following line seperator.
 
0.14  Added support for res.handlers.
      Switched to java.util.regex for the tag parser.
      Fixed a bug with escaped quotes in macro parameters.
      Reformated template generating code to be more readable.
 
0.13  Added support for properties in <% %>-tags (not only macros)
      and fixed an incompatibility with the Rhino Debugger and jstl templates.
 
0.12  Fixed various bugs that were introduced in the 0.11 rewrite.
 
0.11  Replaced all the dirty hacks for keeping track of template line numbers
      and linking them to code line numbers by a clean implementation of the
      same functionality. The result is a faster and less resource hungry parser.
      Cleaned up the code, seperated tag parser form line parser.
 
0.10  More speed improvements, parsing is now around 6-7 times faster than in 0.8
 
0.9  Various speed improvements, leading to an overall decrease of parsing time
      by more than factor 3.
 
0.8  Added support for HopObject collections in foreach
      Removed regexp filter for if() expressions that sometimes seemed to deadlock
      Fixed a bug with finding the right template in the inheritance chain if it
      was overriden by another one.
 
0.7  Fixed problems with error reports. Full stacktraces are now printed, and errors
      caused in macros called from the template are now properly detected and reported.
      Switched to using Context.evaluateString() instead of eval(), for improve of speed
      and reception of proper exceptions.
 
0.6  fixed a bug that caused macros in  objects other than 'this' and 'root' to fail.
 
0.5  first public release.

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 %>
         
      <% end %>
      </td>
      <td nowrap><%= topic.createDate.format("dd.MM.yy - HH:mm") %></td>
    </tr>
  <% end %>
  </table>

=====Sub Templates:

Definition:

  <% template "name">
  This is a sub-template. It can call <% macros %> and write <%= param.value %> too!
  </%>

Rendering:

  <% render "name" value="variables" %>

Rendering of normal templates:

  <% this.render "name" foo="bar" %>

Definition and rendering into a variable in one command:

  <% set "text">
  Render this text into 'name'. <% macros %> can be called.
  </%>

This can now be used like a normal variable, and for example be passed as
the default value in another macro:

  <%= text %>

  <% do_something default=text %>

     removed
     added