David Kaye (@dfkaye) http://github.com/dfkaye
Presented at East Bay Modern Web Application Developers meetup, October 8, 2014
David Kaye (@dfkaye) http://github.com/dfkaye
Presented at East Bay Modern Web Application Developers meetup, October 8, 2014
Templates are big-ticket features in current MVC JavaScript frameworks
Honorable mention for its very small size: riot.js
Text embedded in custom type (not executed) <script> tags
<script type="text/x-handlebars-template">
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
</script>
Template text is "compiled" into a "partial" (a function that accepts some context or data to be merged with the template, returning new text):
var source = $("#entry-template").html();
var template = Handlebars.compile(source);
var context = {title: "My New Post", body: "This is my first post!"}
var html = template(context);
…which renders as:
<div class="entry">
<h1>My New Post</h1>
<div class="body">
This is my first post!
</div>
</div>
Not to pick on Handlebars as they've covered a lot of territory, but Handlebars 2.0 is 98kb; 45kb compressed (14kb gzipped).
(That, and the source is hard to read as it is generated by a build process.
Download size can impact user experience (YMMV)
Can be mitigated with smaller libraries (riot.js, absurdjs template, e.g.).
(welcome to the web :).
Can be mitigated by pre-compiling partials on the server into separate or combined JavaScript files.
Uses the Function ()
constructor to embed the text inside
the partial function
Content Security Policy in browsers prohibits
eval()
and Function()
Can be mitigated by pre-compiling partials on the server.
Data is normally handled by a Model while rendering is normally handled by a View. Template engines tend to blur this separation.
{{#if items}}
{{#each items}}
<li>{{agree_button}}</li>
{{/each}}
{{/if}}
Can't mitigate that with pre-compiling or build processes.
Maybe
Terence Parr, (@the_antlr_guy) argues that templates are documents with "holes" and should contain no business logic.
Parr has implemented this strict separation in his own StringTemplate project at http://www.stringtemplate.org/ , for java (with ports for C#, Python).
Hence, …
Repo at github: https://github.com/dfkaye/stringtemplate
Mocha suite on rawgit: https://rawgit.com/dfkaye/stringtemplate/master/test/mocha/browser-suite.html
Function()
constructor callsscript
elementsA single method added to native String objects
String.prototype.template(data, fn?)
var html = 'some html with $data$'.template(data);
$value.identifier.which.may.be.nested$
var data = { name: 'johnny' };
var string = '$name$';
var name = string.template(data); // => 'johnny'
If no tokens are found or data is not an actual non-null Object, then no transformation occurs. The original string is returned.
denoted by start $name#$ and end $/name#$tags, each containing a trailing #
Accessing each element in the collection with $.$ or $.keyname$
var data = { names: ['johnny', 'janie'] };
var string = '<ul>$name#$ <li>$.$</li> $/name#$</ul>';
var names = string.template(data);
Result:
<ul> <li>johnny</li> <li>janie</li> </ul>
var data = { who : [
{ first: 'johnny', last: 'depp'},
{ first: 'denzel', last: 'washington'}
] };
var string = ['<ul>$who#$',
' <li>$.last$, $.first$</li> ',
'$/who#$</ul>'].join('\n');
var who = string.template(data);
Result:
<ul>
<li>depp, johnny</li>
<li>washington, denzel</li>
</ul>
Many false starts (iterations) caused by
Cannot support mixed data types
[ 'a', 2, true ]
works[ {name: 'a' }, {name: 'b' }]
works[ 'a', {name: 'jed' } ]
with
<li>$.$</li> produces <li>a</li> <li>[object Object]</li>
[ 'a', {name: 'jed' } ]
with
<li>$.name$</li> produces <li>undefined</li> <li>jed</li>
Large strings require a docstring helper, so I've added one, based on mstring https://github.com/rjrodger/mstring by Richard Rodger (@rjrodger):
Function.prototype.template(data, fn?)
Same signature as String#template. Calls String#template internally if a data argument is provided; otherwise returns the docstring without modification.
function fn() {
/***
This is a function template for $name$.
***/
fn.template({name: 'david'});
// => 'This is a function template for david.'
template()
can be called without a data argument,
returning just the docstringWe'll see how that goes…
listing some that came up at the meetup