Helma Logo
main list history
previous version  overview  next version

Version 38 by Lehni on 13. April 2007, 18:07

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.

With revision 0.21 came many changes that reflect the recent *addition of new skin features in Helma 1.6|http://dev.helma.org/wiki/New+Skin+Features+in+Helma+1.6/*.

<% this.attachments %>

Revisions:
   
0.21  Major rewrite and refactoring of most parts, adaption of Hannes' latest
      additions to Helma skins (sub templates, nested macros, filters),
      varios design changes (e.g. only allow to define and set $-variables for
      inside templates).
  
0.20  Refactored the code rendering code to be more readibly and smaller.
      Removed Helma dependency, by using preprocessing macros.
  
0.19  Added support for sub templates and a special macro
      for both rendering sub templates and external templates.
  
0.18  Added support for encoding parameter, fixed a bug in <% else %>
  
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 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 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;% $variable = otherVariable [ + some arithmetic ] %&gt;,</tt> e.g. <tt>&lt;% $count = this.nodes.count() %&gt;</tt>
* <tt>&lt;% macro foo="bar" array=[1,2,3] hash={a:1, b: 2, c:3} nested=&lt;% param.value %&gt; | filter param="string" %&gt;</tt>: calls the macro. Supported are: Global macros, object  macros and res.handlers (e.g. this.macro, root.macro, etc.); prefix, suffix, default, encoding parameters; Neted macros; Filter chains. This 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. Filters, parameters and nested macros work just as in macro calls. This is here mostly for backward compatibility. The prefered and faster way of printing simple 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 yet link to 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>
      <% $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>

====Swallow CR LF after tags:

Any tag can end with -%> to indicated that it does not want the new line after
it beig being rendered. By default. All the control macros do so by default:

  <%= "Hel" -%>
  <%= "ma" %>

Resulting in:

  Helma

====Sub Templates:

The implementation of sub templates has change a lot to reflect the new skin
features outlined here. Sub template are to be added at the end of files:
http://dev.helma.org/wiki/New+Skin+Features+in+Helma+1.6/

Definition:

  <% #name %>
  This is a sub-template. It can call <% macros %> and write <%= param.value %> too!
  Empty lines before and after it are automatically removed.

  <% #greedy +%>
  This is another sub-template. As apposed to the other one, this one keeps the
  empty lines due to the +.

Rendering:

  <% this.template "#name" value="variables" %>

Rendering of normal templates:

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

Definition of a template and rendering into a scope variable in one command:

  <% $text %>
  Render this text into '$text'. Again, <% macros %> can be called.

This can then 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