Skip to content

Original JST Reference

toddfast edited this page Mar 13, 2013 · 1 revision

Overview

The processing instructions that define the results of jstProcess for a template are encoded as attributes in template HTML elements. There are eight such special attributes: jsselect, jsdisplay, jsskip, jscontent, jsvars, jsvalues, jseval, and transclude. Before you dive into the details of individual instructions, however, you should know a little bit about the namespace within which these instructions are processed.

Processing Environment

With the single exception of the transclude instruction, the values of all JsTemplate attributes will contain javascript expressions. These expressions will be evaluated in an environment that includes bindings from a variety of sources, and names defined by any of these sources can be referenced in Jst attribute expressions as if they were variables:

  • JsEvalContext data: All the properties of the JsEvalContext's data object are included in the processing environment.
  • Explicitly declared variables: The setVariable(variableName, variableValue) method of JsEvalContext creates a new variable with the name variableName in the processing environment if no such variable exists, assigning it the value variableValue. If the variable already exists, it will be reassigned the value variableValue. Variables can also be created and assigned with the jsvalues instruction (see below).
    • Note that variables defined in either of these ways are distinct from the JsEvalContext data object. Calling setVariable will not alter the data wrapped by the JsEvalContext instance. This fact can have important consequences when template processing is traversing the hierarchy of the data object (through the use of the jsselect instruction, for example -- see below): no matter what portion of the data hierarchy has been selected for processing, variables created with setVariable will always be available for use in template processing instructions.
  • Special variables: Jst also defines three special variables that can be used in processing instruction attributes:
  • this: The keyword this in JsTemplate attribute expressions will evaluate to the element on which the attribute is defined. In this respect JsTemplate attributes mirror event handler attributes like onclick.
  • $index: Array-valued data can result in a duplicate template node being created for each array element (see jsselect, below). In this case the processing environment for each of those nodes includes $index variable, which will contain the array index of the element associated with the node.
  • $this: $this refers to the JsEvalContext data object used in processing the current node. So in the above example we could substitute $this.end for end without changing the meaning of the jscontent expression. This may not seem like a very useful thing to do in this case, but there are other cases in which $this is necessary. If the JsEvalContext contains a value such as a string or a number rather than an object with named properties, there is no way to retrieve the value using object-property notation, and so we need $this to access the value. So if you have the template
<div id="witha">
  <div id="Hey" 
       jscontent="this.parentNode.id + this.id + dataProperty + $this.dataProperty + declaredVar"
  ></div>
</div> 

and you process it with the statements

var mydata = {dataProperty: 'Nonny'};
var context = new `JsEvalContext`(mydata);
context.setVariable('declaredVar', 'Ho');
var template = document.getElementById('witha');
jstProcess(context, template); 

then the document will display the string withaHeyNonnyNonnyHo. [ 03-environ.html ] The values of "id" and "parentNode.id" are available as properties of the current node (accessible through the keyword this), the value of dataProperty is available (via both a naked reference and the special variable $this) because it is defined in the JsEvalContext's data object, and the value of declaredVar is available because it is defined in the JsEvalContext's variables.

In the discussion of specific instruction attributes below, the phrase"current node" refers to the DOM element on which the attribute is defined.

jscontent

This attribute is evaluated as a javascript expression in the current processing environment. The string value of the result then becomes the text content of the current node. So the template

<div id="tpl"> Welcome 
  <span jscontent="$this">
   (This placeholder name will be replaced by the actual username.) 
  </span>
</div> 

when processed with the javascript statements

var tplData = "Joe User";
var input = new `JsEvalContext`(tplData);
var output = document.getElementById('tpl');
jstProcess(input, output); 

will display

Welcome Joe User

in the browser. [ 04-jscontent.html ] Note the use of $this here: the JsEvalContext constructor is passed the string "Joe User", and so this is the object to which $this refers.

When the Jst processor executes a jscontent instruction, a new text node object is created with the string value of the result as its nodeValue, and this new text node becomes the only child of the current node. This implementation ensures that no markup in the result is evaluated.

jsselect

The primary function of JsTemplate is to create mappings between data structures and HTML representations of those data structures. The jsselect attribute handles much of the work of defining this mapping by allowing you to associate a particular subtree of the data with a particular subtree of the template's DOM structure. When a template node with a jsselect attribute is processed, the value of the jsselect attribute is evaluated as a javascript expression in the current processing environment, as described above. If the result of this evaluation is not an array, the Jst processor automatically creates a new JsEvalContext object to wrap the result of the evaluation. The processing environment for the current node now uses this new JsEvalContext rather than the original JsEvalContext.

For example, imagine that you have the following data object (wrapped in a JsEvalContext object constructed with ``JsEvalContext(tplData)):

var tplData = { 
  username:"Jane User", 
  addresses:[ {
    location:"111 8th Av.",
    label:"NYC front door"
  }, {
    location:"76 9th Av.",
    label:"NYC back door"
  }, {
    location:"Mountain View",
    label:"Mothership"
  } ]
}; 

and you use this data in processing the template

<div id="tpl">
  <span jsselect="username" jscontent="$this"></span>'s Address Book </div> 

The jsselect attribute tells the processor to retrieve the username property of the data object, wrap this value ("Jane User") in a new JsEvalContext, and use the new JsEvalContext in processing the span element. As a result, $this refers to "Jane User" in the context of the span, and the jscontent attribute evaluates to "Jane User."

Note that the jsselect has to be executed before the jscontent in order for this example to work. In fact, jsselect is always evaluated before any other JsTemplate attributes (with the exception of transclude), and so the processing of all subsequent instructions for the same template element will take place in the new environment created by the jsselect (see "Order of Evaluation" below).

What happens if you try to jsselect the array-valued addresses property of the data object? If the result of evaluating a jsselect expression is an array, a duplicate of the current template node is created for each item in the array. For each of these duplicate nodes a new JsEvalContext will be created to wrap the array item, and the processing environment for the duplicate node now uses this new JsEvalContext rather than the original. In other words, jsselect operates as a sort of "for each" statement in the case of arrays. So you can expand your address book template to list the addresses in your data object like so:

<div id="tpl">
<h1><span jsselect="username" jscontent="$this">User de Fault</span>'s Address Book </h1>
<table cellpadding="5">
<tr>
  <td>
    <h2>Location:</h2>
  </td>
  <td>
    <h2> ;Label:</h2>
  </td>
</tr>
<tr jsselect="addresses">
  <td jscontent="location"></td>
  <td jscontent="label"></td>
</tr>
</table>
</div> 

Processing this template with your Jane User address book data will produce a nice table with a row for each address. [ 05-jsselect.html ] Since the execution of a jsselect instruction can change the number of children under a template node, we might worry that if we try to reprocess a template with new data the template will no longer have the structure we want. JsTemplate manages this problem with a couple of tricks. First, whenever a jsselect produces duplicate nodes as a result of an array-valued expression, the Jst processor records an index for each node as an attribute of the element. So if the duplicate nodes are reprocessed, the processor can tell that they started out as a single node and will reprocess them as if they are still the single node of the original template. Second, a template node is never entirely removed, even if a jsselect evaluates to null. If a jsselect evaluates to null (or undefined), the current node will be hidden by setting "display='none'", and no further processing will be performed on it. But the node will still be present, and available for future reprocessing.

jsdisplay

The value of the jsdisplay attribute is evaluated as a javascript expression. If the result is false, 0, "" or any other javascript value that is true when negated, the CSS display property of the current template node will be set to 'none', rendering it invisible, and no further processing will be done on this node or its children. This Jst instruction is particularly useful for checking for empty content. You might want to display an informative message if a user's address book is empty, for example, rather than just showing them an empty table. The following template will accomplish this goal:

<div id="tpl">
<h1><span jsselect="username" jscontent="$this">User de Fault</span>'s Address Book </h1>
<span jsdisplay="addresses.length==0">Address book is empty.</span> <table cellpadding="5" jsdisplay="addresses.length">
<tr>
  <td><h2>Location:</h2></td>
  <td><h2> ;Label:</h2></td>
</tr>
<tr jsselect="addresses">
  <td jscontent="location"></td>
  <td jscontent="label"></td>
</tr>
</table>
</div> 

If the addresses array is empty, the user will see "Address book is empty," but otherwise they will see the table of addresses as usual. [ 06-jsdisplay.html, 07-jsdisplay-empty.html ]

transclude

As jsselect does, the transclude instruction expands the structure of a template. Transclude does so by copying a structure from some other element in the document. The value of the transclude attribute is interpreted as an element id literal rather than as a javascript expression. (This difference, by the way, is the reason for the absence of a "js" in its name.)

If an element with the given id exists in the document, it is cloned and the clone replaces the node with the transclude attribute. Template processing continues on the new element. If no element with the given id exists, the node with the transclude attribute is removed. No further processing instruction attributes will be evaluated on a node if it has a transclude attribute.

The transclude attribute allows for recursion, because a template can be transcluded into itself. This feature can be handy when you want to display hierarchically structured data. If you have a hierarchically structured table of contents, for example, recursive transclude statements allow you represent the arbitrarily complex hierarchy with a simple template:

// Hierarchical data:
var tplData = {
  title: "JsTemplate",
  items: [ {
    title: "Using JsTemplate",
    items: [ { 
      title: "The JsTemplate Module"
    }, { 
      title: "Javascript Data"
    }, { 
      title: "Template HTML"
    }, { 
      title: "Processing Templates with Javascript Statements"
    } ]
  }, {
    title: "Template Processing Instructions",
    items: [ { 
      title: "Processing Environment" 
    }, { 
      title: "Instruction Attributes", 
      items: [ {
        title: "jscontent"
      }, {
        title: "jsselect"
      }, {
        title: "jsdisplay"
      }, {
        title: "transclude"
      }, {
        title: "jsvalues"
      }, {
        title: "jsskip"
      }, {
        title: "jseval"
      } ]
    } ]
  } ]
};
<div id="tpl">
<span jscontent="title">Outline heading</span>
<ul jsdisplay="items.length">
  <li jsselect="items">
    <!--Recursive tranclusion:-->
    <div transclude="tpl"></div>
  </li>
</ul>
</div> 

The recursion in this example terminates because eventually it reaches data objects that have no "items" property. When the jsselect asks for "items" on one of these leaves, it evaluates to null and no further processing will be performed on that node. Note also that when the node with a transclude attribute is replaced with the transcluded node in this example, the replacement node will not have a transclude attribute.

How to Use JsTemplate described the use of the jstGetTemplate function to process a copy of a template rather than the original template. Templates with recursive transcludes must be cloned in this way before processing. Because of the internal details of Jst processing, a template that contains a recursive reference to itself may be processed incorrectly if the original template is processed directly. The following javascript code will perform the required duplication for the above template:

var PEG_NAME = 'peg';
var TEMPLATE_NAME = 'tpl';
// Called by the body onload handler:
function jsinit() {
  pegElement = domGetElementById(document, PEG_NAME);
  loadData(pegElement, TEMPLATE_NAME, tplData);
}

function loadData(peg, templateId, data) {
  // Get a copy of the template:
  var templateToProcess = jstGetTemplate(templateId);
  // Wrap our data in a context object:
  var processingContext = new JsEvalContext(data);
  // Process the template
  jstProcess(processingContext, templateToProcess);
  // Clear the element to which we'll attach the processed template: 
  peg.innerHTML = '';
  // Attach the template:
  appendChild(peg, templateToProcess);
} 

[ 08-transclude.html ]

jsvalues

The jsvalues instruction provides a way of making assignments that alter the template processing environment. The template processor parses the value of the jsvalues attribute value as a semicolon-delimited list of name value pairs, with every name separated from its value by a colon. Every name represents a target for assignment. Every value will be evaluated as a javascript expression and assigned to its associated target. The nature of the target depends on the first character of the target name:

  • If the first character of the target name is "$", then the target name is interpreted as a reference to a variable in the current JsEvalContext processing environment. This variable is created if it doesn't already exist, and assigned the result of evaluating its associated expression. It will then be available for subsequent template processing on this node and its descendants (including subsequent name-value pairs in the same jsvalues attribute). Note that the dollar sign is actually part of the variable name: if you create a variable with jsvalues="$varname:varvalue", you must use $varname to retrieve the value.
  • If the first character of the target name is ".", then the target name is interpreted as a reference to a javascript property of the current template node. The property is created if it doesn't already exist, and is assigned the result of evaluating its associated expression. So the instruction jsvalues=".id:'Joe';.style.fontSize:'30pt'" would change the id of the current template node to "Joe" and change its font size to 30pt. [ 09-jsvalues.html ]
  • If the first character of the target name is neither a dot nor a dollar sign, then the target name is interpreted as a reference to an XML attribute of the current template element. In this case the instruction jsvalues="name:value" is equivalent to the javascript statement this.setAttribute('name','value'), where this refers to the current template node. Just as in the case of a call to setAttribute, the value will be interpreted as a string (after javascript evaluation). So jsvalues="sum:1+2" is equivalent to this.setAttribute('sum', '3').

The jsvalues instruction makes a handy bridge between the DOM and Jst data. If you want a built-in event handler attribute like onclick to be able to access the currently selected portion of the JsEvalContext data, for example, you can use jsvalues to copy a reference to the data into an attribute of the current element, where it will be accessible in the onclick attribute via this. The following example uses this approach to turn our outline into a collapsible outline:

// Function called by onclick to record state of closedness and
// refresh the outline display
function setClosed(jstdata, closedVal) {
  jstdata.closed = closedVal;
  loadData(PEG_ELEMENT, TEMPLATE_NAME, tplData);
}
<div id="tpl"> <!-- Links to open and close outline sections: -->
<a href="#" jsdisplay="closed" 
 jsvalues=".jstdata:$this"
 onclick="setClosed(this.jstdata,0)">[Open]</a>
<a href="#" jsdisplay="!closed && items.length"
 jsvalues=".jstdata:$this"
 onclick="setClosed(this.jstdata,1)">[Close]</a>
<span jscontent="title">Outline heading</span>
<ul jsdisplay="items.length && !closed">
  <li jsselect="items">
    <!--Recursive tranclusion:-->
    <div transclude="tpl"></div>
  </li>
</ul>
</div> 

[ 10-jsvalues.html ]

jsvars

This instruction is identical to jsvalues, except that all assignment targets are interpreted as variable names, whether or not they start with a "$." That is, all assignment targets are interpreted as described in section 1 of the jsvalues section above.

jseval

The Jst processor evaluates a jseval instruction as a javascript expression, or a series of javascript expressions separated by semicolons. The jseval instruction thus allows you to invoke javascript functions during template processing, in the usual template processing environment, but without any of the predefined template processing effects of jsselect, jsvalues, jsdisplay, jsskip, or jscontent. For example, with the addition of a jseval instruction to our outline title span, the Jst processor can record a count of the total number of outline items with and without titles as it traverses the data hierarchy.

The count information in this example is stored in the processing context with a call to setVariable, so that it will be available to template processing throughout the data hierarchy:

processingContext.setVariable('$counter', counter); 

A jseval expression increments the count:

<span jscontent="title"
  jseval="title? $counter.full++: $counter.empty++"> Outline heading </span> 

and then a separate template displays these counts at the bottom of the page:

<div id="titleCountTpl">
<p>This outline has <span jscontent="$counter.empty"></span>
empty titles and <span jscontent="$counter.full"></span>
titles with content.</p>
</div> 

Note that when you close headings the counts change: jsdisplay is not only hiding the closed elements, but also aborting the processing of these elements, so that the jseval expressions on these elements are never evaluated. [ 11-jseval.html ]

jsskip

The value of the jsskip attribute is evaluated as a javascript expression. If the result is any javascript value that evaluates to true in a boolean context, then the Jst processor will not process the subtree under the current node. This instruction is useful for improving the efficiency of an application (to avoid unnecessarily processing deep trees, for example).

The effect of a jsskip that evaluates to true is very similar to the result of a jsdisplay that evaluates to false. In both cases, no processing will be performed on the node's children. Jsskip will not, however, prevent the current node from being displayed.

Order of Evaluation

JsTemplate instruction attributes within a single element are evaluated in the following order:

  • transclude. If a transclude attribute is present no further Jst attributes are processed.
  • jsselect. If jsselect is array-valued, remaining attributes will be copied to each new duplicate element created by the jsselect and processed when the new elements are processed.
  • jsdisplay
  • jsvars
  • jsvalues
  • jseval
  • jsskip
  • jscontent