-
Notifications
You must be signed in to change notification settings - Fork 12
Cht concepts
The purpose of a template engine in a Web application is to produce page content by inserting data into an HTML-like boilerplate. Depending on the complexity and flexibility of template descriptions, they may cross the range from a glorified formatting string to a full blown programming language. The Comprehensive HTML Template library sits on the “far right” of this range, providing a language with rich facilities and extensive runtime that can, nevertheless, be seamlessly integrated with the Javascript application code.
CHT templates are usually organized in modules: text files with an extension .cht
. Each file consists of one or more separate named templates which are conceptually equivalent to Javascript functions, though templates may not nest.
The template language is a composition of plain HTML, CHT elements that have syntax reminiscent of XML instructions and the substitutions enclosed in doubled-up curly braces and containing Q+ queries which may be as simple as a single property reference or incorporate complex dataflow pipelines. The sample template below lists all property names and values from a Javascript object it expects to receive as an argument in an HTML table:
<? template PropTable ?>
<table>
<tr><th>#</th><th>Name</th><th>Value</th></tr>
<tr><td colspan="3"><hr></td></tr>
<? foreach "keys:$" ?>
<tr><td>{{$#+1}}.</td><td>{{$}}</td><td>{{$0[$]}}</td></tr>
<? /foreach ?>
</table>
<? /template ?>
An individual CHT template is not only similar to a Javascript function syntactically: for all intents and purposes it is a Javascript function that accepts a number of arguments and returns the resulting HTML content. From the example above it is easy to surmise that HTML parts of the template are included in the result literally, substitution expressions are computed with results appearing in their place and the CHT elements determine the control flow providing conditional evaluation and iterative capability.
The reality is somewhat more complex than last sentence would seem to imply. The HTML embedded in a template is actually split apart into markup and what looks like English text, making it possible to replace English strings with their localized counterparts at the time the template is compiled. The substitution results are automatically encoded to escape any characters that may break the resulting HTML. Both CHT elements and Q+ queries ultimately rely on the dataflow architecture of the jtlc compiler to provide richer and more flexible data transformation facilities than simple loops over arrays of data. Finally, the return value of a template is not simply a text string or a DOM fragment — though either can be obtained from it — it is a runtime object with a number of interesting capabilities above and beyond simple HTML rendering.
Fortunately, it is easy to see just what a template becomes after compilation by looking it up in the FireBug script panel (if you’re running Mozilla FireFox) or in the script section of developer tools (if your browser of choice is Google Chrome). In either case, the example above would look like a script with a name <? PropTable ?>
:
(function(_a,_b,_c,_d,_e,_f,_g){function $self(){
var $=arguments,b,d,e,f;
b=_b.refID++;
d=[];
d.push("<table> <tr><th>#</th><th>");
d.push("Name");
d.push("</th><th>");
d.push("Value");
d.push("</th></tr> <tr><td colspan=\"3\"><hr></td></tr>");
e=_c($[0]);
for(f=0;f<e.length;++f){
d.push("<tr><td>");
d.push(_d("{0}.",String(f+1).replace(_e,_f)));
d.push("</td><td>");
d.push(String(e[f]).replace(_e,_f));
d.push("</td><td>");
d.push(String($[0][e[f]]).replace(_e,_f));
d.push("</td></tr>");
}
d.push("</table>");
if(!b)_b.refID=0;
return new _g({
_split_text:d
});
} return $self;})
//@ sourceURL=[CHT-Templates]/<?PropTable?>
As with any compiled code, the result is far from being self-explanatory but usually transparent enough to catch errors in a debugging session. Note that the outer function simply provides a closure binding various functions and objects to local variables beginning with an underscore to make the code execute faster; it is the function labeled $self()
that becomes the template body.
Like functions, templates may reference each other. You may, for example, want to rewrite the above example separating the table header from the body for clarity:
<? template PropTable ?>
<table>
<? PropTableHeader ?>
<? PropTableBody ?>
</table>
<? /template ?>
<? template PropTableHeader ?>
<tr><th>#</th><th>Name</th><th>Value</th></tr>
<tr><td colspan="3"><hr></td></tr>
<? /template ?>
<? template PropTableBody ?>
<? foreach "keys:$" ?>
<tr><td>{{$#+1}}.</td><td>{{$}}</td><td>{{$0[$]}}</td></tr>
<? /foreach ?>
<? /template ?>
Yet, when compiled, <? PropTable ?>
produces exactly the same code as before! Well, by default, simple templates are expanded in-line rather than form separate functions. More complex cases may behave differently and explicit control over inlining is also available.
Note that <? PropTable ?>
does not explicitly pass its argument into <? PropTableBody ?>
even though the latter clearly required access to it. This is an artifact of the important design choice: even though you can pass multiple arguments into a template from the Javascript code, only the first one becomes current input in jtlc terms or $
in Q+ terms. The rest can be accessed as $1
through $9
but their values are global not only for the template originally invoked but also for any templates it may reference. Only the current input, $
, may be changed in a nested template invocation — but where it does not need to change, you don’t have to provide $
as the [trivial] argument.
Now, how seamless is that integration between CHT and Javascript, really? Here’s what you need to do to call the sample template from the previous section:
- Create the root HTML file that would load Dojo and configure the module map. Provide
<div id="sample"></div>
in it so there’s a place to render the template into. - Save the template(s) above into a CHT file, for example
sample.cht
. Place it into a subdirectory calledCHT/
relative to your root HTML file to minimize the effort required to set up the Dojo module map. - Create the root Javascript file (say,
sample.js
) that would be loaded from the root HTML file withdojo.require('sample.js')
:
1 Always load your Javascript files with dojo.require() rather than a <script>
element to save yourself the trouble with cross-domain loader!
dojo.require( 'dojox.jtlc.CHT.loader' );
dojo.ready( function() {
dojox.jtlc.CHT.loader.get( 'sample.PropList' )(
{ aString: "A string", aNumber: 12345 }
).render( 'sample' );
} );
Apparently, executing a template is a three-step affair:
- Load and compile the template evaluator: this is what the CHT loader is for. It maintains a cache of both the loaded template modules and the compiled templates, so these expensive preparations are never repeated. Its
get()
method simply returns a function that your template has become and that you can call from the application code. - Execute the evaluator: pass in the data (first argument will become the current input), get back the result also known as the template instance.
- Do something with the result. In most cases, you simply
render()
it into its proper place within DOM by passing in an element ID or the element itself (other options are described here).
2 In this example, the template module is not yet loaded at the time get()
is called and therefore the latter can only complete asynchronously. This does not prevent the next two calls from working due to a funky provision in the CHT loader, but the entire sequence including the call to render()
will also be completing asynchronously after template loading and parsing are done.
How do you reference Javascript code from a template then? There are two basic ways:
- Global functions, or members of objects accessible from global scope (such as Dojo namespaces), can be called directly from Q+ expressions or do blocks; they can also be used as custom filters. These functions will be called while template is evaluated.
- Instances of global classes (usually created with help of dojo.declare()) will be automatically instantiated by the Dojo parser invoked from
render()
method of template instances. Their methods may be connected to DOM events associated with the HTML content produced by the template. Follow this link for details.