Helma Logo
main list history

JavaScript Template Engine

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

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.

nametypesize
Template-helma.jsundefined bytes
Template-rhino.jsundefined bytes
Template-browser.jsundefined bytes
Template-unprocessed.jsundefined bytes
Template-20.jsundefined bytes

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.

HopObject.prototype.renderTemplate(name, param, out)

Overview of Statements

Features

Examples

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

Result:

  Hello World
Loops
  <table>
  <% foreach $topic in param.topic %>
    <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>

Loops support prefix, suffix, default and separator:

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

Result:

  [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?

E.g.:

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

Result:

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

Revisions

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

Links to this page: Status Update, Related Projects