jsTenjin FAQ

release: 0.0.3
last update: $Date: 2007-08-04 10:02:36 +0900 (Sat, 04 Aug 2007) $

Release: 0.0.3

Table of contents:

Basic

I got an SyntaxError exception.

Command-line option '-z' checks syntax of template file. You should check template by it.

File 'ex1.jshtml':
<ul>
<?js for (var i = 0, i <= 10, i++) { ?>
  <li>#{i}</li>
<?js } ?>
</ul>
Result:
$ jstenjin -z ex1.jshtml
[NG] ex1.jshtml
ex1.jshtml:2: SyntaxError: missing ; after for-loop initializer
ex1.jshtml:2:  for (var i = 0, i <= 10, i++) {

'#{_content}' includes extra newline at end. Can I delete it?

Yes. You can use '<?js echo(_content); ?>' or '<?js _buf.push(_content); ?>' instead of '#{_conent}'.

File 'ex2-layout.jshtml':
<!-- -->
#{_content}
<!-- -->

<!-- -->
<?js echo(_content); ?>
<!-- -->

<!-- -->
<?js _buf.push(_content); ?>
<!-- -->
File 'ex2-content.jshtml':
foo
bar
baz
Result:
$ jstenjin --layout=ex2-layout.jshtml ex2-content.jshtml
<!-- -->
foo
bar
baz

<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

<!-- -->
foo
bar
baz
<!-- -->

Can I change 'escapeXml()' function name?

Yes. You can change them by setting escapefunc option for Tenjin.Template() or Tenjin.Engine().

File 'ex3.js':
load('tenjin.js');
var engine = new Tenjin.Engine({escapefunc:'.escapeHTML'});
var template = engine.getTemplate('ex3.jshtml');
print(template.script);
File 'ex3.jshtml':
Hello ${name}!
Result:
$ js ex3.js
var _buf = [];  _buf.push('Hello ', (name).escapeHTML(), '!\n');
_buf.join('')

Command-line option '--escapefunc=name' is equivarent to the above.

Result:
$ jstenjin -s --escapefunc=.escapeHTML ex3.jshtml
var _buf = [];  _buf.push('Hello ', (name).escapeHTML(), '!\n');
_buf.join('')

Can I change '_buf' variable name?

No. Variable name '_buf' should not and will never be changed.

Is it able to specify default value if a variable is not set?

Yes, by _context['varname']. It is able to check whether value is set or not by inspecting special variable '_context' which represents context values.

File 'ex4.jshtml':
<?js var user = typeof(_context.user) == 'undefined' ? 'Guest' : _context.user; ?>
Hello ${user}!
Result:
$ jstenjin -c 'user:"Tenjin"' ex4.jshtml
Hello Tenjin!
$ jstenjin ex4.jshtml
Hello Guest!

Template

Is it possible to specify variables passed to template?

Yes. You can specify template arguments by '<?js //@ARGS arg1, arg2, arg3 ?>'.

File 'ex5-layout.jshtml'
<?xml version="1.0 ?>
<?js //@ARGS x, y ?>
<p>
  x = #{x}
  y = #{y}
  z = #{z}
</p>

Template arguments line is converted into local variable assignment statements.

Source code
$ jstenjin -s ex5.jshtml
var _buf = [];  _buf.push('<?xml version="1.0 ?>\n');
 var x = _context['x']; var y = _context['y'];
 _buf.push('<p>\n\
  x = ', x, '\n\
  y = ', y, '\n\
  z = ', z, '\n\
</p>\n');
_buf.join('')

Undeclared arguments are not available even when they are passed via context object.

Result:
$ jstenjin -c 'x:10, y:20, z:30' ex5.jshtml
*** Error:
ex5.jshtml:3: ReferenceError: "z" is not defined.

Is it able to change embedded expression pattern?

Yes, you can create subclass of Template class and override embedded expression pattern.

ex6-expr-pattern.jshtml:
<p>HTML escaped: ${value}</p>
<p>not escaped:  #{value}</p>
<p>not escaped:  <%= value %></p>
ex6-expr-pattern.js:
load('tenjin.js');

var MyTemplate = function(properties) {
    Tenjin.Template.call(this, properties);
}

MyTemplate.prototype = new Tenjin.Template();

// embedded expression pattern
MyTemplate.prototype.expressionPattern = /([$#])\{(.*?)\}|<%=((.|\n)*?)%>/g;

// return expression string and flag whether escape or not from matched object
MyTemplate.prototype.getExpressionAndEscapeflag = function(matched) {
    var expr, escapeflag;
    if (matched[1]) {
        expr = matched[2];
	escapeflag = matched[1] == '$';
    }
    else {
        expr = matched[3];
	escapeflag = false;
    }
    return [expr, escapeflag];
};

// test program
var context = { value: 'AAA&BBB' };
var engine = new Tenjin.Engine({templateclass: MyTemplate});
var output = engine.render('ex6-expr-pattern.jshtml', context);
print(output);
Result:
$ js ex6-expr-pattern.js
<p>HTML escaped: AAA&amp;BBB</p>
<p>not escaped:  AAA&BBB</p>
<p>not escaped:  AAA&BBB</p>

Does jsTenjin support M17N?

No, but it is easy to support M17N. The point is:

  • Change cache filename according to language. For example, create cache file 'file.jshtml.en.cache', 'file.jshtml.fr.cache', 'file.jshtml.it.cache', and so on from template file 'file.jshtml'.
  • Create Engine object for each language.
  • (optinal) Use preprocessing for performance reason.

The following is an example to generate M17N pages from a template file.

ex7-m18n.jshtml:
<div>
<?JS // '_()' is a translation method ?>
 <p>${{_('Hello')}} ${username}!</p>
</div>
ex7-m18n.js:
load('tenjin.js');

///
/// message catalog to translate message
///
var MESSAGE_CATALOG = {
  en: { 'Hello'    : 'Hello',
        'Good bye' : 'Good bye' },
  fr: { 'Hello'    : 'Bonjour',
        'Good bye' : 'Au revoir' }
};

function translation_func(lang) {
    var f = function(message_key) {
        var dict = MESSAGE_CATALOG[lang] || {};
	return dict[message_key] || message_key;
    };
    return f;
}


///
/// engine class which supports M17N
///
var M17NEngine = function(properties) {
    Tenjin.Engine.call(this, properties);
    if (properties.lang)
        this.lang = properties.lang;
};

M17NEngine.prototype = new Tenjin.Engine();

M17NEngine.prototype.lang = 'en';       // default language

/// change cache flename to 'file.html.lang.cache'
M17NEngine.prototype.cachename = function(filename) {
    return filename+'.'+this.lang+'.cache';
};

/// set translation function to context object
M17NEngine.prototype.hookContext = function(context) {
    context = Tenjin.Engine.prototype.hookContext(context);
    context._ = translation_func(this.lang);  // set _() to context
    return context;
};


///
/// test program
///
var template_name = 'ex7-m18n.jshtml';
var context = { username: 'World' };

/// engine for english
var engine = new M17NEngine({preprocess:true, cache:true});
var output = engine.render(template_name, context);   // same template
print("--- lang:", engine.lang, "---");
print(output);

/// engine for French
var engine = new M17NEngine({preprocess:true, cache:true, lang:'fr'});
var output = engine.render(template_name, context);   // same template
print("--- lang:", engine.lang, "---");
print(output);
Result:
$ js ex7-m18n.js
--- lang: en ---
<div>
 <p>Hello World!</p>
</div>

--- lang: fr ---
<div>
 <p>Bonjour World!</p>
</div>

jsTenjin doesn't provide M17N feature directly because requirements for M17N are different for each applications or frameworks. Some applications or frameworks adapt GetText library and others use its original M17N library. What jsTenjin should do is not to provide M17N feature but to show an example to support M17N.

Layout Template

Can I change layout template name in a template file?

Yes. If you set _context._layout, its value is regarded as layout template name.

  • You can specify template file name (ex. 'user_list.jshtml') or template short name (ex. ':list').
  • If you set true to '_context._layout', default layout template name is used instead.
  • It is able to N-th level nested template.

See the next section for details.

Can I nest layout templates for any depth?

Yes. If you set _context._layout, you can nest layout templates in any depth.

The following example shows that:

  • 'ex7-content.jshtml' uses 'ex7-mylayout.jshtml' as layout template.
  • 'ex7-mylayout.jshtml' uses 'ex7-baselayout.jshtml' as layout template.
File 'ex8-content.jshtml':
<?js _context.title = 'Changing Layout Template Test'; ?>
<?js /// specify layout template name ?>
<?js _context._layout = 'ex8-mylayout.jshtml'; ?>
foo
bar
baz
File 'ex8-mylayout.jshtml':
<?js /// use default layout template name ?>
<?js _context._layout = true; ?>
<div id="content">
<?js echo(_content); ?>
</div>
File 'ex8-baselayout.jshtml':
<html>
  <body>
<?js if (typeof(_context['title']) != 'undefined') { ?>
    <h1>${title}</h1>
<?js } ?>
<?js echo(_content); ?>
  </body>
</html>
Result:
$ jstenjin --layout=ex8-baselayout.jshtml ex8-content.jshtml
<html>
  <body>
    <h1>Changing Layout Template Test</h1>
<div id="content">
foo
bar
baz
</div>
  </body>
</html>

Can I disable default layout template for a certain template?

Yes. If you set false to _context._layout, default layout template will not be applied.

Is Django-like "Template Inheritance" supported?

No, but you can emulate it partially by combination of template capturing and '_context._layout'.

File 'ex9-baselayout.jshtml':
<html>
 <body>

<?js /// if variable 'header_part' is defined then print it, ?>
<?js /// else print default header part. ?>
  <div id="header">
<?js startPlaceholder('header_part'); ?>
   <img src="img/logo.png" alt="logo" ?>
<?js stopPlaceholder(); ?>
  </div>

<?js /// main content part ?>
  <div id="content">
<?js echo(content_part); ?>
  </div>

<?js /// if variable 'footer_part' is defined then print it, ?>
<?js /// else print default footer part. ?>
  <div id="footer">
<?js startPlaceholder('footer_part'); ?>
   <hr />
   <em>webmaster@example.com</em>
<?js stopPlaceholder(); ?>
  </div>
  
 </body>
</html>
File 'ex9-customlayout.jshtml':
<?js /// '_context._layout' is equivarent to '{% extends "foobar.html" %}' ?>
<?js /// in Django template engine. ?>
<?js _context._layout = 'ex9-baselayout.jshtml'; ?>

<?js /// you can override header or footer by capturing. ?>
<?js startCapture('footer_part'); ?>
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
<?js stopCapture(); ?>
File 'ex9-content.jshtml':
<?js /// '_context._layout' variable is equivarent to '{% extends "foobar.html" %}' ?>
<?js /// in Django template engine. ?>
<?js _context._layout = 'ex9-customlayout.jshtml'; ?>

<?js /// main content part ?>
<?js startCapture('content_part'); ?>
<ul>
<?js for (var i = 0, n = items.length; i < n; i++) { ?>
  <li>${items[i]}</li>
<?js } ?>
</ul>
<?js stopCapture(); ?>

'startPlaceholder()' and 'stopPlaceholder()' are pre-defined helper macros. For example,

<?js startPlaceholder('header_part'); ?>
   <img src="img/logo.png" alt="logo" ?>
<?js stopPlaceholder(); ?>

is equivarent to the following.

<?js if (typeof(_context['header_part'])!='undefined') { ?>
<?js     _buf.push(_context['header_part']); ?>
<?js } else { ?>
   <img src="img/logo.png" alt="logo" ?>
<?js } ?>

The following is the result. It shows that footer part in baselayout is overrided by other templates.

Result:
$ jstenjin -c "items:['AAA','BBB','CCC']" ex9-content.jshtml
<html>
 <body>

  <div id="header">
   <img src="img/logo.png" alt="logo" ?>
  </div>

  <div id="content">
<ul>
  <li>AAA</li>
  <li>BBB</li>
  <li>CCC</li>
</ul>
  </div>

  <div id="footer">
<address style="text-align:right">
  copyright&copy; 2007 kuwata-lab all rights reserved<br />
  <a href="webmaster&#64;kuwata-lab.com">webmaster&#64;kuwata-lab.com</a>
</address>
  </div>
  
 </body>
</html>

Performance

How fast is jsTenjin compared with other solutions?

jsTenjin contains benchmark script. This shows that jsTenjin works much faster than other solutions.

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Memory 2GB
$ cd pltenjin-X.X.X/benchmark
$ js -h
JavaScript-C 1.6 pre-release 1 2006-04-04 (OSSP js 1.6.20070208)
usage: js [-PswWxC] [-b branchlimit] [-c stackchunksize] [-v version] [-f script
file] [-e script] [-S maxstacksize] [scriptfile] [scriptarg...]
$ make N=10000 JS=js
time js bench.js -n 10000
        0.01 real         0.01 user         0.00 sys
time js bench.js -n 10000 tenjin-cached
       19.00 real        15.14 user         3.73 sys
time js bench.js -n 10000 tenjin-nocache
       29.78 real        26.74 user         2.92 sys
time js bench.js -n 10000 tenjin-reuse
       12.98 real        12.43 user         0.47 sys

$ java -version
java version "1.5.0_07"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-164)
Java HotSpot(TM) Client VM (build 1.5.0_07-87, mixed mode, sharing)
$ make N=10000 JS='java -server -jar js.jar'
time java -server -jar js.jar bench.js -n 10000
        0.69 real         0.58 user         0.06 sys
time java -server -jar js.jar bench.js -n 10000 tenjin-cached
       24.29 real        23.15 user         1.22 sys
time java -server -jar js.jar bench.js -n 10000 tenjin-nocache
       42.05 real        40.59 user         1.61 sys
time java -server -jar js.jar bench.js -n 10000 tenjin-reuse
       19.15 real        18.59 user         0.42 sys

In addition, module size of jsTenjin is small, and it is very light-weight to import it. This is important for CGI program. Other solutions may be very heavy to import the module and suitable only for apache module or FastCGI.

Why jsTenjin is so fast?

Because it doesn't use template engine original language.

Other template engines, such as Template-Toolkit(perl), Django(python), or Smarty(php), has their original languages. This is not good idea for script language because:

  • They are slow.
  • Implementation will be complex.
  • Learning cost is high.

In addition, jsTenjin is faster than Jakarta Velocity which is a very popular template engine in Java. (It means that dynamic Java is slower than script languages.)

Template engine should use their host language directly unless there are some kind of reasons.