Created
September 27, 2012 00:41
-
-
Save phiggins42/3791511 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define([ | |
"dojo", | |
"dijit", | |
"dijit/_Templated" | |
], function(dojo,dijit,_Templated){ | |
/* | |
mustache.js — Logic-less templates in JavaScript | |
See http://mustache.github.com/ for more info. | |
*/ | |
var Mustache = function() { | |
// Checks whether a value is thruthy or false or 0 | |
function is_kinda_truthy(bool) { | |
return bool === false || bool === 0 || bool; | |
} | |
// summary: Get an object from a string representation of that object, eg: "foo.bar.baz" | |
function getObject(name, create, context){ | |
// name: String | |
// The dot-notated name of the object to obtain | |
// create: Boolean? | |
// Toggle if unfound objects along the path should be created during lookup | |
// context: Object? | |
// An optional relative context. | |
// | |
// example: | |
// | var x = { a:{ b:{ c:{ d:10 }}}}; | |
// | var y = getObject("a.b.c.d"); // 10 | |
var parts = name.split("."), obj = context || window; | |
for(var i = 0, p; obj && (p = parts[i]); i++){ | |
obj = (p in obj ? obj[p] : (create ? obj[p] = {} : undefined)); | |
} | |
return obj; // mixed | |
} | |
var Renderer = function() {}; | |
Renderer.prototype = { | |
otag: "{{", | |
ctag: "}}", | |
pragmas: {}, | |
buffer: [], | |
pragmas_implemented: { | |
"IMPLICIT-ITERATOR": true | |
}, | |
context: {}, | |
render: function(template, context, partials, in_recursion) { | |
// reset buffer & set context | |
if(!in_recursion) { | |
this.context = context; | |
this.buffer = []; // TODO: make this non-lazy | |
} | |
// fail fast | |
if(!this.includes("", template)) { | |
if(in_recursion) { | |
return template; | |
} else { | |
this.send(template); | |
return; | |
} | |
} | |
template = this.render_pragmas(template); | |
var html = this.render_section(template, context, partials); | |
if(in_recursion) { | |
return this.render_tags(html, context, partials, in_recursion); | |
} | |
this.render_tags(html, context, partials, in_recursion); | |
}, | |
/* | |
Sends parsed lines | |
*/ | |
send: function(line) { | |
if(line != "") { | |
this.buffer.push(line); | |
} | |
}, | |
/* | |
Looks for %PRAGMAS | |
*/ | |
render_pragmas: function(template) { | |
// no pragmas | |
if(!this.includes("%", template)) { | |
return template; | |
} | |
var that = this; | |
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + | |
this.ctag); | |
return template.replace(regex, function(match, pragma, options) { | |
if(!that.pragmas_implemented[pragma]) { | |
throw({message: | |
"This implementation of mustache doesn't understand the '" + | |
pragma + "' pragma"}); | |
} | |
that.pragmas[pragma] = {}; | |
if(options) { | |
var opts = options.split("="); | |
that.pragmas[pragma][opts[0]] = opts[1]; | |
} | |
return ""; | |
// ignore unknown pragmas silently | |
}); | |
}, | |
/* | |
Tries to find a partial in the curent scope and render it | |
*/ | |
render_partial: function(name, context, partials) { | |
name = this.trim(name); | |
if(!partials || partials[name] === undefined) { | |
throw({message: "unknown_partial '" + name + "'"}); | |
} | |
if(typeof(context[name]) != "object") { | |
return this.render(partials[name], context, partials, true); | |
} | |
return this.render(partials[name], context[name], partials, true); | |
}, | |
/* | |
Renders inverted (^) and normal (#) sections | |
*/ | |
render_section: function(template, context, partials) { | |
if(!this.includes("#", template) && !this.includes("^", template)) { | |
return template; | |
} | |
var that = this; | |
// CSW - Added "+?" so it finds the tighest bound, not the widest | |
var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + | |
"\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + | |
"\\s*", "mg"); | |
// for each {{#foo}}{{/foo}} section do... | |
return template.replace(regex, function(match, type, name, content) { | |
var value = that.find(name, context); | |
if(type == "^") { // inverted section | |
if(!value || that.is_array(value) && value.length === 0) { | |
// false or empty list, render it | |
return that.render(content, context, partials, true); | |
} else { | |
return ""; | |
} | |
} else if(type == "#") { // normal section | |
if(that.is_array(value)) { // Enumerable, Let's loop! | |
return that.map(value, function(row) { | |
return that.render(content, that.create_context(row), | |
partials, true); | |
}).join(""); | |
} else if(that.is_object(value)) { // Object, Use it as subcontext! | |
return that.render(content, that.create_context(value), | |
partials, true); | |
} else if(typeof value === "function") { | |
// higher order section | |
return value.call(context, content, function(text) { | |
return that.render(text, context, partials, true); | |
}); | |
} else if(value) { // boolean section | |
return that.render(content, context, partials, true); | |
} else { | |
return ""; | |
} | |
} | |
}); | |
}, | |
/* | |
Replace {{foo}} and friends with values from our view | |
*/ | |
render_tags: function(template, context, partials, in_recursion) { | |
// tit for tat | |
var that = this; | |
var new_regex = function() { | |
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + | |
that.ctag + "+", "g"); | |
}; | |
var regex = new_regex(); | |
var tag_replace_callback = function(match, operator, name) { | |
switch(operator) { | |
case "!": // ignore comments | |
return ""; | |
case "=": // set new delimiters, rebuild the replace regexp | |
that.set_delimiters(name); | |
regex = new_regex(); | |
return ""; | |
case ">": // render partial | |
return that.render_partial(name, context, partials); | |
case "{": // the triple mustache is unescaped | |
return that.find(name, context); | |
default: // escape the value | |
return that.escape(that.find(name, context)); | |
} | |
}; | |
var lines = template.split("\n"); | |
for(var i = 0; i < lines.length; i++) { | |
lines[i] = lines[i].replace(regex, tag_replace_callback, this); | |
if(!in_recursion) { | |
this.send(lines[i]); | |
} | |
} | |
if(in_recursion) { | |
return lines.join("\n"); | |
} | |
}, | |
set_delimiters: function(delimiters) { | |
var dels = delimiters.split(" "); | |
this.otag = this.escape_regex(dels[0]); | |
this.ctag = this.escape_regex(dels[1]); | |
}, | |
escape_regex: function(text) { | |
// thank you Simon Willison | |
if(!arguments.callee.sRE) { | |
var specials = [ | |
'/', '.', '*', '+', '?', '|', | |
'(', ')', '[', ']', '{', '}', '\\' | |
]; | |
arguments.callee.sRE = new RegExp( | |
'(\\' + specials.join('|\\') + ')', 'g' | |
); | |
} | |
return text.replace(arguments.callee.sRE, '\\$1'); | |
}, | |
/* | |
find `name` in current `context`. That is find me a value | |
from the view object. check local context first, then global | |
context just in case. PH: added dot-notation support, which | |
follows the same pattern, though allows any dotted variable | |
to be looked up from the current context (relative) and, if | |
unfound, the global context. | |
*/ | |
find: function(name, context) { | |
name = this.trim(name); | |
var value, candidate = name == "." ? context[name] : getObject(name, null, context); | |
if(is_kinda_truthy(candidate)) { | |
value = candidate; | |
} else { | |
candidate = getObject(name, null, this.context); // this assumes `this` is view | |
if(is_kinda_truthy(candidate)) { | |
value = candidate; | |
} | |
} | |
if(typeof value === "function") { | |
return value.apply(context); | |
} | |
if(value !== undefined) { | |
return value; | |
} | |
// silently ignore unkown variables | |
return ""; | |
}, | |
// Utility methods | |
/* includes tag */ | |
includes: function(needle, haystack) { | |
return haystack.indexOf(this.otag + needle) != -1; | |
}, | |
/* | |
Does away with nasty characters | |
*/ | |
escape: function(s) { | |
s = String(s === null ? "" : s); | |
return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { | |
switch(s) { | |
case "&": return "&"; | |
case "\\": return "\\\\"; | |
case '"': return '\"'; | |
case "<": return "<"; | |
case ">": return ">"; | |
default: return s; | |
} | |
}); | |
}, | |
// by @langalex, support for arrays of strings | |
create_context: function(_context) { | |
if(this.is_object(_context)) { | |
return _context; | |
} else { | |
var iterator = "."; | |
if(this.pragmas["IMPLICIT-ITERATOR"]) { | |
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; | |
} | |
var ctx = {}; | |
ctx[iterator] = _context; | |
return ctx; | |
} | |
}, | |
is_object: function(a) { | |
return a && typeof a == "object"; | |
}, | |
is_array: function(a) { | |
return Object.prototype.toString.call(a) === '[object Array]'; | |
}, | |
/* | |
Gets rid of leading and trailing whitespace | |
*/ | |
trim: String.prototype.trim ? function(s){ | |
return s.trim(); | |
} : function(s) { | |
return s.replace(/^\s*|\s*$/g, ""); | |
}, | |
map: Array.prototype.map ? function(array, fn){ | |
return array.map(fn); | |
} : function(array, fn) { | |
/* | |
Why, why, why? Because IE. Cry, cry cry. | |
*/ | |
var r = [], l = array.length; | |
for(var i = 0; i < l; i++) { | |
r.push(fn(array[i])); | |
} | |
return r; | |
} | |
}; | |
return({ | |
name: "mustache.js", | |
version: "0.3.1-dev", | |
/* | |
Turns a template and view into HTML | |
*/ | |
to_html: function(template, view, partials, send_fun) { | |
var renderer = new Renderer(); | |
if(send_fun) { | |
renderer.send = send_fun; | |
} | |
renderer.render(template, view, partials); | |
if(!send_fun) { | |
return renderer.buffer.join("\n"); | |
} | |
} | |
}); | |
}(); | |
return dojo.declare(_Templated, { | |
// this prevents _Templated from storing the template as a domNode to clone() | |
// which helps in templates not requiring substitution, but we'll assume we always need to | |
_skipNodeCache:true, | |
_stringRepl: function(template){ | |
// here's where the `magic` happens. return the processed template. use `this` as the view, | |
// our instance. also allow mixing in partials: | |
var rendering = Mustache.to_html(template, this, this.partials); | |
return dojo.trim(rendering); // trim whitespace | |
} | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment