Skip to content

Instantly share code, notes, and snippets.

@aron
Created September 16, 2010 11:52
Show Gist options
  • Save aron/582305 to your computer and use it in GitHub Desktop.
Save aron/582305 to your computer and use it in GitHub Desktop.
/* A lightweight templating system.
*
* Based on Tim (http://github.com/premasagar/tim) and Mustache
* (http://github.com/janl/mustache.js).
*
* Template supports simple value replacement as well as block
* functions, iterators and conditionals
*
* Template strings can contain either plain tokens `{{token}}`
* which are replaced with their equivilent value in the data
* object or blocks `{%block%}` which can wrap a portion of your
* template.
*
* Whitespace in tokens is ignored. Blocks must be closed using
* either "end" or a slash "/" followed by the block name. eg.
* `{% myblock %}Content{% /myblock %}`
* or
* `{% myblock %}Content{% end myblock %}`
*
* Arguments
*
* string - A template String to convert.
* data - A data Object containing key/value pairs to supplant.
*
* Simple Examples
*
* template('{{name}}', {name: 'Aron'});
* //=> "Aron"
*
* template('{{user.age}}', {name: 'Aron', age: 25});
* //=> 25
*
* Iterator Examples
*
* All values in array will be passed into a new template call with one
* key, `item`, this will equal the current array item. If a block is
* used its contents will be used as the template string.
*
* template('a {{item}}, ', {animals: ['cat', 'dog', 'mouse']});
* //=> "a cat, a dog, a mouse, "
*
* template('<ul>{% animals %}<li>{{item.name}}</li>{% end animals %}</ul>', {
* animals: [{name: 'cat'}, {name: 'dog'}, {name: 'mouse'}]
* });
* //=> "<ul><li>cat</li><li>dog</li><li>mouse</li></ul>"
*
* Boolean Example
*
* If true a boolean block will display its contents.
*
* template('{%is_admin?%}<a href="Edit">Edit {{type}}</a>{%end is_admin?%}', {
* 'is_admin?': true, type: 'User'
* });
* //=> <a href="Edit">Edit User</a>
*
* Callback Example
*
* Functions receive the block contents as a parameter and can
* return any string value. The callbacks context (this) is bound
* to the data object passed in to the template.
*
* template('{%callback%}<p>Some template string</p>{%end callback%}', {
* callback: function (tmpl) {
* // this === data
* // tmpl === "<p>Some template string</p>"
* return 'Whatever';
* }
* });
* //=> "Whatever"
*
* Returns supplanted template String.
*/
(function (namespace, context) {
var keypath = '\\s*([a-z0-9_$][.a-z0-9_$?]*)\\s*',
token_regex = new RegExp('{{' + keypath + '}}', 'gi'),
block_regex = new RegExp('{%' + keypath + '%}([\\s\\S]*){%\\s*(?:end|\\/)\\s*\\1\\s*%}', 'gi'),
isArray;
isArray = Array.isArray || function (o) {
return Object.prototype.toString.call(o) === '[object Array]';
};
context[namespace] = function template(string, data) {
function getValueForToken(match, token, contents) {
var path = token.split('.'),
length = path.length,
lookup = data || {},
i = 0;
contents = contents || '';
for (; i < length; i+=1) {
lookup = lookup[path[i]];
if (i === length - 1){
switch (typeof lookup) {
case 'string':
case 'number':
return lookup;
case 'boolean':
return lookup ? contents : '';
case 'function':
return lookup.call(data, contents);
case 'object':
if (isArray(lookup)) {
return (function iterateDataArray() {
var items = [],
length = lookup.length,
i = 0;
for (; i < length; i+=1) {
items.push(template(contents || '{{item}}', {item:lookup[i]}));
}
return items.join('');
})();
}
}
}
}
return match;
}
return string.replace(block_regex, getValueForToken).replace(token_regex, getValueForToken);
};
})('template', this);
(function(m,n){var g="\\s*([a-z0-9_$][.a-z0-9_$?]*)\\s*",o=new RegExp("{{"+g+"}}","gi"),p=new RegExp("{%"+g+"%}([\\s\\S]*){%\\s*(?:end|\\/)\\s*\\1\\s*%}","gi"),h;h=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"};n[m]=function e(q,i){function j(r,c,b){c=c.split(".");var k=c.length,a=i||{},d=0;for(b=b||"";d<k;d+=1){a=a[c[d]];if(d===k-1)switch(typeof a){case "string":case "number":return a;case "boolean":return a?b:"";case "function":return a.call(i,b);case "object":if(h(a))return function t(){for(var l=
[],s=a.length,f=0;f<s;f+=1)l.push(e(b||"{{item}}",{item:a[f]}));return l.join("")}()}}return r}return q.replace(p,j).replace(o,j)}})("template",this);
var data = {
name: 'Aron',
age: 25,
bio: 'A web developer living in Brighton, UK',
items: ['shoe', 'hat', 'coat', 'watch', 'umbrella'],
'is_admin?': false,
comments: function (tmpl) {
// Do something complicated.
return 'No Comment';
}
};
console.log(template(
'<p>{{name}} is {{age}} and a {{bio}}. He has the following possessions:</p>\n' +
'<ul>' +
'{% items %}\n <li>a {{item}}</li>{% enditems %}\n' +
'</ul>\n' +
'{% is_admin? %}\n<a href="login>Login</a>"{% endis_admin? %}' +
'<p>{{comments}}</p>',
data
));
/* Outputs:
<p>Aron is 25 and a A web developer living in Brighton, UK. He has the following possessions:</p>
<ul>
<li>a shoe</li>
<li>a hat</li>
<li>a coat</li>
<li>a watch</li>
<li>a umbrella</li>
</ul>
<p>No Comment</p>
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment