Helma Logo
main list history
previous version  overview  next version

Version 54 by Lehni on 22. April 2007, 07:23

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/*.

Due to all these changes, I have decided to keep the last revision before the changes here (Template-20.js). Also, I uploaded the unprocessed file for those interested in seeing how I am using the cpp preprocessor from gcc among with jsstrip.py and sed to produce the final files. I will write more about this soon.

<% this.attachments %>

====Helma Integration

Template.js needs to be placed in the global protoype.
It adds the function renderTemplate() to the HopObject prototype, that can be then called on any HopObject.

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

* <tt>name</tt> is the name of the skin, without the extension. "jstl" is used as extension for the template files, which stands for "JavaScript Template Language".
* <tt>param</tt> is the optional parameter, which can be accessed from the templates, just like in Helma skins.
* <tt>out</tt> 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;% 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 link to res.data, req.data, session.data.


* Macros with conditional statements and loops can define prefix, suffix default and encoding parameters too.
* Loops can define an additional paraemter called separator, which will be added between entries, but only if there was output.
* Any macro that ends with -%&gt; swallows the following line break.
* Loop variables give access to the state of the loop: $variable#isFirst, #isLast, #index, #length.


=====Conditional statements

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

Conditional statements support prefix, suffix and default:

  <% if param.greetings prefix="Hello" %>
  <% end %>


  Hello World


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

Loops support prefix, suffix, default and separator:

  <% foreach $value in [1, 2, 3] prefix="[ " suffix=" ]" separator=", " %>
      <%= $value %>
  <% end %>


  [1, 2, 3]

During every for loop these helper variables are available:

  $variable#index // Index of the current iteration
  $variable#length // Length of the entire for loop
  $variable#isFirst // Is this the first iteration?
  $variable#isLast // Is this the last iternation?


  <% foreach $item in param.list %>
      <%= $item#index %>
      <%= $item#length %>
      <%= $item#isFirst %>
      <%= $item#isLast %>
  <% end %>

=====Swallow Line Breaks

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" %>



=====Sub Templates

The implementation of sub templates has changed a lot to reflect the new skin features outlined here.
Sub template are to be added at the end of files:


  <% #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 +.


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

Rendering of normal templates:

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

=====Sub Templates Variables

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

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

These can then be used like normal variables, and for example be passed as the default value in another macro. Note that althought the definition is at the end, the variable is valid in the whole scope.

  <%= $text %>
  <% macro_call default=$text %>

A tag ending with -%> is trimmed after rendering, so that an empty template really
results in an empty string:

  <% $empty -%>


0.24  Added browser support. Tested on Safari and Firefox.
      Fixed a bug in conditional statements containing '=='.
0.23  Added loop variables: $variable#index, #length, #isFirst, #isLast.
      Many bug fixes.
0.22  More clean-ups in macro parsing code, distinction between control tags
      and macro tags is now only on the handling level, not parsing level.
      This adds feature like prefix / suffix to foreach and if / elseif, and
      resulted in cleaner code.
      Additionally, the separator parameter can now be set in foreach.
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 swallow 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 swallow 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.