| @@ -2,67 +2,77 @@ | |||||
| Shamless port of http://github.com/defunkt/mustache | Shamless port of http://github.com/defunkt/mustache | ||||
| by Jan Lehnardt <jan@apache.org> | by Jan Lehnardt <jan@apache.org> | ||||
| Thanks @defunkt for the awesome code | |||||
| Thanks @defunkt for the awesome code. | |||||
| See http://github.com/defunkt/mustache for more info. | |||||
| */ | */ | ||||
| var Mustache = { | var Mustache = { | ||||
| name: "mustache.js", | name: "mustache.js", | ||||
| version: "0.1", | version: "0.1", | ||||
| debug: true, | |||||
| stack: " ", | |||||
| context: {}, | context: {}, | ||||
| /* | |||||
| Public method. Turns a template and view into HTML | |||||
| */ | |||||
| to_html: function(template, view) { | to_html: function(template, view) { | ||||
| return this.render(template, view); | return this.render(template, view); | ||||
| }, | }, | ||||
| // Private Methods | |||||
| render: function(template, view) { | render: function(template, view) { | ||||
| this.stack = this.stack + " "; | |||||
| // fail fast | // fail fast | ||||
| if(template.indexOf("{{") == -1) { | if(template.indexOf("{{") == -1) { | ||||
| return template; | return template; | ||||
| } | } | ||||
| // keep context around for recursive calls | |||||
| this.context = context = this.merge((this.context || {}), view); | this.context = context = this.merge((this.context || {}), view); | ||||
| // first, render all sections | |||||
| var html = this.render_section(template); | var html = this.render_section(template); | ||||
| // restore context, recursion might have messed it up | // restore context, recursion might have messed it up | ||||
| this.context = context; | this.context = context; | ||||
| // finally, render tags | |||||
| return this.render_tags(html); | return this.render_tags(html); | ||||
| }, | }, | ||||
| /* | |||||
| Tries to find a partial in the global scope and render it | |||||
| */ | |||||
| render_partial: function(name) { | render_partial: function(name) { | ||||
| // FIXME: too hacky | |||||
| var evil_name = eval(name) | var evil_name = eval(name) | ||||
| switch(typeof evil_name) { | switch(typeof evil_name) { | ||||
| case "string": | |||||
| case "string": // a tring partial, we simply render | |||||
| return this.to_html(evil_name, ""); | return this.to_html(evil_name, ""); | ||||
| case "object": | |||||
| case "object": // a view partial needs a `name_template` template to render | |||||
| var tpl = name + "_template"; | var tpl = name + "_template"; | ||||
| return this.to_html(eval(tpl), evil_name); | return this.to_html(eval(tpl), evil_name); | ||||
| default: | |||||
| default: // should not happen #famouslastwords | |||||
| throw("Unknown partial type."); | throw("Unknown partial type."); | ||||
| } | } | ||||
| }, | }, | ||||
| merge: function(a, b) { | |||||
| for(var name in b) { | |||||
| if(b.hasOwnProperty(name)) { | |||||
| a[name] = b[name]; | |||||
| } | |||||
| } | |||||
| return a; | |||||
| }, | |||||
| /* | |||||
| Renders boolean and enumerable sections | |||||
| */ | |||||
| render_section: function(template) { | render_section: function(template) { | ||||
| if(template.indexOf("{{#") == -1) { | if(template.indexOf("{{#") == -1) { | ||||
| return template; | return template; | ||||
| } | } | ||||
| var that = this; | var that = this; | ||||
| // for each {{#foo}}{{/foo}} section do... | |||||
| return template.replace(/\{\{\#(.+)\}\}\s*([\s\S]+)\{\{\/\1\}\}\s*/mg, | return template.replace(/\{\{\#(.+)\}\}\s*([\s\S]+)\{\{\/\1\}\}\s*/mg, | ||||
| function(match, name, content) { | function(match, name, content) { | ||||
| var value = that.find(name); | var value = that.find(name); | ||||
| if(that.is_array(value)) { | |||||
| if(that.is_array(value)) { // Enumerable, Let's loop! | |||||
| return value.map(function(row) { | return value.map(function(row) { | ||||
| return that.render(content, row); | return that.render(content, row); | ||||
| }).join(''); | }).join(''); | ||||
| } else if(value) { | |||||
| } else if(value) { // boolean section | |||||
| return that.render(content); | return that.render(content); | ||||
| } else { | } else { | ||||
| return ""; | return ""; | ||||
| @@ -71,15 +81,13 @@ var Mustache = { | |||||
| ); | ); | ||||
| }, | }, | ||||
| is_array: function(a) { | |||||
| return (a && | |||||
| typeof a === 'object' && | |||||
| a.constructor === Array); | |||||
| }, | |||||
| /* | |||||
| Replace {{foo}} and friends with values from our view | |||||
| */ | |||||
| render_tags: function(template) { | render_tags: function(template) { | ||||
| // values | |||||
| // tit for tat | |||||
| var that = this; | var that = this; | ||||
| // for each {{(!<{)?foo}} tag, do... | |||||
| return template.replace(/\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/mg, | return template.replace(/\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/mg, | ||||
| function (match, operator, name) { | function (match, operator, name) { | ||||
| switch(operator) { | switch(operator) { | ||||
| @@ -95,6 +103,27 @@ var Mustache = { | |||||
| }, this); | }, this); | ||||
| }, | }, | ||||
| /* | |||||
| find `name` in current `context`. That is find me a value | |||||
| from the view object | |||||
| */ | |||||
| find: function(name) { | |||||
| name = this.trim(name); | |||||
| var context = this.context; | |||||
| if(typeof context[name] === "function") { | |||||
| return context[name].apply(context); | |||||
| } | |||||
| if(context[name] !== undefined) { | |||||
| return context[name]; | |||||
| } | |||||
| throw("Can't find " + name + " in " + context); | |||||
| }, | |||||
| // Utility methods | |||||
| /* | |||||
| Does away with nasty characters | |||||
| */ | |||||
| escape: function(s) { | escape: function(s) { | ||||
| return s.toString().replace(/[&"<>\\]/g, function(s) { | return s.toString().replace(/[&"<>\\]/g, function(s) { | ||||
| switch(s) { | switch(s) { | ||||
| @@ -108,19 +137,33 @@ var Mustache = { | |||||
| }); | }); | ||||
| }, | }, | ||||
| find: function(name) { | |||||
| name = this.trim(name); | |||||
| // print(this.stack + "find(" + name + ")"); | |||||
| var context = this.context; | |||||
| if(typeof context[name] === "function") { | |||||
| return context[name].apply(context); | |||||
| } | |||||
| if(context[name] !== undefined) { | |||||
| return context[name]; | |||||
| /* | |||||
| Merges all properties of object `b` into object `a`. | |||||
| `b.property` overwrites a.property` | |||||
| */ | |||||
| merge: function(a, b) { | |||||
| for(var name in b) { | |||||
| if(b.hasOwnProperty(name)) { | |||||
| a[name] = b[name]; | |||||
| } | |||||
| } | } | ||||
| throw("Can't find " + name + " in " + context); | |||||
| return a; | |||||
| }, | |||||
| /* | |||||
| Thanks Doug Crockford | |||||
| JavaScript — The Good Parts lists an alternative that works better with | |||||
| frames. Frames can suck it, we use the simple version. | |||||
| */ | |||||
| is_array: function(a) { | |||||
| return (a && | |||||
| typeof a === 'object' && | |||||
| a.constructor === Array); | |||||
| }, | }, | ||||
| /* | |||||
| Gets rid of leading and trailing whitespace | |||||
| */ | |||||
| trim: function(s) { | trim: function(s) { | ||||
| return s.replace(/^\s*|\s*$/g, ''); | return s.replace(/^\s*|\s*$/g, ''); | ||||
| }, | }, | ||||