Skip to content

rndme/ustache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ustache

mustache templates w/ a micro ES6 core

An experimental 2kb template engine that does 80% of what mustache does, and with extra declarative-view features.

Contents

Design

A RegExp-based transpiler with imports constructs an ES6 template string that evaluates the embeded exressions. It's only about 50 lines of code, making it easy to fork for customized applications. It's written with as few moving peices as possible, with an eye towards customization without complication. - top

Usage

(a string template) to return a function that returns an HTML string when passed an object:

var fnRender = ustache(strTemplate),
strOutput = fnRender(objData);

(a string template and object of data) to invoke immediately:

strOutput=ustache(strTemplate, object);

(a string template, object of data, and object of imports (partials) ) :

strOutput = ustache(strTemplate, objData, objImports);
fnRender = ustache(strTemplate, null, objImports); top

Compared to Mustache, Handlebars, and Hogan

ustache is congruent with Mustache/Hogan, with many added features. It's not logic-less (JS expressions can be used mid-template). ustache needs pure functions, so it uses this in front of plain injections (see below). Those injections are also not HTML escaped by default like Mustache. you can both prefix this and HTML escape injections using the {{=name}} tag.

Mustache also differs with minor features: it's hoisting of context, allowing lambdas in data (functions that return functions), iterating the whole data argument as an array using {{#.}}{{.}}{{/.}}, allowing non-valid property identifiers like {{#person?}}, imports importing imports, and allowing custom delimters, none of which ustache supports. Despite the limits, ustache passes about 3/4 of Mustache's unit tests, with the failing ones mainly relate to auto-escaping and hoisting plus the above-noted differences on seldom-used features.

Handlebars shares many capabilities with ustache, but ustache syntax is more explict on the closing tags, ex. {{/title}}{{/comments}} instead of {{/if}}{{/each}}. ustache is also the only mustache engine (afaik) that uses ES6 template strings instead of a full-blown parser. top

Syntax Features

You can use normal Mustche syntax, with a few exceptions and many additions. See the mustache.js project for basic examples, review the unit tests, run them live, or keep reading.

Injection

{{this.name}} | @name | {{=name}} | {{this.sub.name.prop}} | {{this.user['name']}} | {{this.name.bold()}}
Inserts the value of the named path. Unlike Mustache, you can use bracket notation and invoke methods inline, even passing arguments to methods via primitives or early-run imports. You can perform calculations mid-expression using virtually any valid javascript expression. You can reach not only in-scope variables, but globals like Math.random() as well. Also keep in mind that you can use ES6 template strings to inject dynamic values into the template before it executes.
ex: ustache("Hello {{=name}}", {name: "Fred"}) == "Hello Fred";
ex: ustache("Hello <b>{{this.name}}</b>", {name:"Fred"}) == "Hello <b>Fred</b>";
ex: ustache("Hello @name", {name:"Fred".bold()}) == "Hello <b>Fred</b>";
ex: ustache("Hello {{this.name.bold()}}", {name:"Fred"}) == "Hello <b>Fred</b>";
ex: ustache("Hello {{=name}}", {name:"Fred".bold()}) == "Hello &lt;b&gt;Fred&lt;&#x2F;b&gt;";
ex: ustache("Random Number: {{1/Math.random()}}", {}); // yes, expressions work! top

Imports

{{>name}}
Imports use the same syntax, but work slightly differently than Mustache's partials. Whereas Mustache partials are executed in-flow and with surrounding context, ustache imports run globally just prior to templating. Global (flat) processing executes much faster than Mustache's, but also means that ustache imports cannot import other imports. All imported values should be direct properties of the imports object (no nested sub-objects). As they execute, imports simply replace literal text, with no consideration of context or validity. This is a good thing because you can inject template code, and it will get executed as though it were hard-coded, providing a macro-like stage of code execution. top

Looping

{{#arr}} Array Iteration

{{#users}}
Iterates data Arrays and repeats their content for each element in the Array. Due to the simplicity of the engine, there is on one restriction on nested looping: you cannont have a duplicated property name iterated on different nested levels; eg. {users:{users:[1,2,3]}} is no good. Note that # tags also provide other capabilities: they acti like switches if fed a boolean, and they drill into nested data objects if they point to an object.
ex: ustache("{{#numbers}}#{{/numbers}}", {numbers:[11,22,33]}) == "###";
ex: ustache("{{#numbers}}{{.}} {{/numbers}}", {numbers:[11,22,33]}) == "11 22 33 ";
ex: ustache("{{#numbers}}{{.}}{{SEP}}, {{/SEP}}{{/numbers}}", {numbers:[11,22,33]}) == "11, 22, 33";
ex: ustache("{{#numbers}}{{INDEX}}:{{.}} {{/numbers}}", {numbers:[11,22,33]}) == "1:11 2:22 3:33 ";
ex: ustache("{{#numbers}}{{INDEX}}:{{.}}{{SEP}}, {{/SEP}}{{/numbers}}", {numbers:[11,22,33]}) == "1:11, 2:22, 3:33";
ex: ustache("{{#numbers}}{{ (this.i%2) ? this.i : ''}}{{/numbers}}", {numbers:[{i:11},{i:22},{i:33}]}) == "1133"; top

{{$obj}} Object Iteration

Iterates over objects using a placeholder KEY name within the section tag.
Inside the section KEY will equal the name of the object property's key and . will equal the property value.
ex: ustache('{{$a}}{{KEY}}: {{.}} {{/a}}', {a:{b:1,c:5}}) == "b: 1 c: 5 ";

Conditionals

{{#total}} | {{^total}} | {{#section}}
Same are mustache, if the target property is non falsy, the contents are included.

{{?total>0}} | {{?section=="home"}}
This is a big piece of functionality missing from Mustache.js, and can be quite helpful when constructing views. If the expression returns falsy, the contents are ommited. If the expression is truthy, the contents are included/executed. This can help add "active" classes to navigation and show/hide sections content according to task/location/time/etc. Logic in the template allow declarative view definition and eliminates the need for DOM-binding to achieve view updates.
ex: ustache("i is {{?i > 5}}big{{/i > 5}}{{?i<6}}small{{/i<6}}", {i: 2}) == "i is small";
ex: ustache("i is {{?i > 5}}big{{/i > 5}}{{?i<6}}small{{/i<6}}", {i: 9}) == "i is big"; top

Razor Syntax

@lname, @fname | @#users @INDEX: @name @/users
"Inspired" by MS's VS/MVC razor templates, this alternative syntax can keep visual boilerplate costs down. You can use it for injection, looping, and conditionals, but the allowed characters are more restricted that the traditional {{}} delimiters (\w\.$|) , so it's not the best choice for complex logic. Lastly, it avoids mistaking email addresses for tokens by insisting upon a non-wordy char to the left of the @.
ex: ustache("Hello @name", {name: "Fred"}) == "Hello Fred";
ex: ustache("Chars: @name.length", {name: "Fred"}) == "Chars: 4";
ex: ustache("Hello @user.name", { user: {name: "Fred"} }) == "Hello Fred"; top

{{|path}} else syntax

{{|path}} turns into {{/path}}{{^path}}, for simpler else handling of regular mustache conditionals.
ex: ustache("{{#i}}yes{{|i}}no{{/i}}", {i: 9}) == "yes";
ex: ustache("{{#i}}yes{{|i}}no{{/i}}", {i: 0}) == "no"; top

{{INDEX}}

Simple "contstant" that returns the current index when iterating an Array.
ex: ustache("{{#numbers}}{{INDEX}}:{{.}} {{/numbers}}", {numbers:[11,22,33]}) == "1:11 2:22 3:33 "
top

{{SEP}} mini-section

Simple "constant" that returns the enclosed block for every value except for the last.
ex: ustache("{{#numbers}}{{.}}{{SEP}}, {{/SEP}}{{/numbers}}", {numbers:[11,22,33]}) == "11, 22, 33";
top

{{__.key}} root syntax

{{__.key}} reaches key on the data object given to ustache, bypassing local conflicts.
ex: ustache('{{#b}}{{a}}|{{__.a}} {{/b}}', {a:123, b:[{a:1}]} ) == "1|123 ";
top

{{SCOPE}} state object

SCOPE is a maleable internal object available to templates. You can use it to store view state like scroll positions, checkboxes options, active section, etc. The object is auto-created or passed during the initial call to ustache(), and inherited by imports and all code in the template, allowing templates to act more like components. You can reach this object as SCOPE from any injection, section, or conditional template tag.
ex: ustache("{{SCOPE.nick=this.name.bold(),''}} Hello {{SCOPE.nick}}", {name:"Fred"}) == " Hello <b>Fred</b>";
see SCOPE used to make simple components at an online SCOPE demo



top