Simple and robust browser side template processing for
Ajax based web applications
Steffen Meschkat, Google
WWW 2008, April 21-25, Beijing, China
Why another template processing idiom?
Template processing is the staple pattern for
separation of data and presentation in web applications.
Works on one page at a time — inadequate
for incremental, asynchronous page updates typical of Ajax
applications. Therefore:
- Incremental processing — every processing
operation produces valid output.
- Differential processing — output text is
again a template.
Fix other undesirable properties of standard
template processing:
- Wellformed output guaranteed.
- Escaped by default.
- Intelligible templates — input template is valid
output.
And, of course:
- Pure javascript, HTML, browser side processing.
This looks good in theory. But does it work in practice?
Template processing input
template
<div id="t">
<div jsselect="items" jscontent="$this"></div>
</div>
data
var input = {
items: ['A', 'B', 'C']
};
API
var output = jstGetTemplate('t');
jstProcess(new JsExprContext(input), output);
Template processing output
output
<div>
<div jsinstance="0"
jsselect="items" jscontent="$this">A</div>
<div jsinstance="1"
jsselect="items" jscontent="$this">B</div>
<div jsinstance="*2"
jsselect="items" jscontent="$this">C</div>
</div>
Differential processing
data
var input = {
items: ['A', 'BC']
};
output
<div>
<div jsinstance="0"
jsselect="items" jscontent="$this">A</div>
<div jsinstance="*1"
jsselect="items" jscontent="$this">BC</div>
</div>
Differential processing, again
data
var input = {
items: []
};
output
<div>
<div jsinstance="*0" jsselect="items"
style="display: none" jscontent="$this">A</div>
</div>
Template composition
template
<div id="zebralist">
<div jsselect="items">
<div jsdisplay="$index % 2 == 0"
style="background-color:#f0fff0"
jscontent="$this"></div>
<div jsdisplay="$index % 2 != 0"
style="background-color:#ffffff"
jscontent="$this"></div>
</div>
</div>
template
<div id="t1">
<div transclude="zebralist"></div>
</div>
Attributes summary
jsselect="expr" — iteration,
change of local evaluation context
jsdisplay="expr" —
conditional display
jscontent="expr" —
content of current DOM node
jsvalues="target:expr;..."
— set values in current context
jsvars="var:expr;..."
— set variables with names that don't start with
$
jseval="expr" — evaluates
expression on current DOM node
transclude="id" —
transcludes an HTML fragment by id (no expression here)
Target summary
Selecting which values to set in current context:
jsvalues="attr:expr;..."
— set attribute values
jsvalues=".prop:expr;..."
— set js properties
jsvalues=".prop.subprop:expr;..."
— set nested js properties (e.g., on
.style)
jsvalues="$var:expr;..."
— set variables in local context
Expression summary
$this — local evaluation context
$index — position of local context in
iteration implied by jsselect (analog
position() in XSLT)
this — current output context (DOM
node)
$top — initial evaluation context
(passed to new JsEvalContext())
- properties of local evaluation context
- variables defined by
jsselect,
jsvars
- variables defined externally by
JsEvalContext.prototype.setVariable()
- globals defined externally by
JsEvalContext.setGlobal()
- Javascript expressions
API summary
new JsEvalContext(data: Object)
JsEvalContext.prototype.setVariable(
name: string, value: Object)
JsEvalContext.setGlobal(name: string, value: Object)
jstGetTemplate(name: string): Element
jstProcess(input: JsEvalContext, output: Element)
Inspiration & alternatives
- Ctemplate, PHP, clearsilver:
placeholder-structured; may provide server side source of
jstemplates.
- Zope TAL: inspired attribute embedding.
- XSLT: inspired wellformedness guarantees and local
evaluation context — but also the desire for
intelligible template language and a concise expression
language.
- NXSL: earlier used in maps on Safari, like jstemplate
but with xpath binding to XML input data.
Content
ctemplate
<b>{{ATTACHMENT_NAME_ESCAPED}}</b> -
{{ATTACHMENT_SIZE_ESCAPED}}
{{ATTACHMENT_NAME_ESCAPED}} -
{{ATTACHMENT_SIZE_ESCAPED}}
clearsilver
<b><?cs var:html_escape(attachment.name)?></b> -
<?cs var:html_escape(attachment.size)?>
jstemplate
<b jscontent="attachment.name">attachment.html</b> -
<span jscontent="attachment.size">100k</span>
Iteration
ctemplate
<ul>
{{#ITEM_SECTION}}<li>{{ITEM}}</li>{{/ITEM_SECTION}}
<ul>
clearsilver
<ul>
<?cs each:item = items?><li><?cs var:item?></li><?cs /each?>
</ul>
jstemplate
<ul>
<li jsselect="items" jscontent="$this"></li>
</ul>
Attribute values
ctemplate
<a href="{{URL}}">here</a>
clearsilver
<a href="<?cs var:url ?>">here</a>
jstemplate
<a href="#example" jsvalues="href:url">here</a>
XSLT
<a>
<xsl:text>here</xsl:text>
<xsl:attribute name="href">
<xsl:value-of select="@url"/>
</xsl:attribute>
</a>
XSLT
<a href="{@url}">here</a>
This looks good in practice. But does it work in theory?
Leverage
Reuses existing concepts and their implementations:
- HTML syntax as template processing instruction container.
- Javascript syntax as value expressions.
- URL syntax for composition expressions.
- DOM semantics for escaping.
Just adds concepts, doesn't replace:
- Processing instruction attributes.
- Specific value expression values.
- Processing target expressions.
- Composition semantics.
Invariants and Guarantees
Robustness:
- Processing preserves wellformedness.
- Composition by
transclude preserves
wellformedness.
Security:
- DOM provides fully correct escaping automatically.
Simplicity:
- Input is valid instance of output document type.
- Output is valid instance of input template type.
- Processing is idempotent.
Conclusion
Yes. It works in practice.
Yes. It works in theory.
Specifically, reuse and leverage:
- Concepts, syntax, code.
- Simplicity vs. reuse.
- Leverage, don't layer.