├── License.txt ├── jQueryMustache.js ├── mustache.js └── test.html /License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkdevcode/jQuery-Mustache/6f759832300f72cb7a89b8caf75b712434432377/License.txt -------------------------------------------------------------------------------- /jQueryMustache.js: -------------------------------------------------------------------------------- 1 | // jQuery Mustache Plug-In 2 | // Version: 1.0.0, Last updated: 2/17/2011 3 | // 4 | // GitHub - https://github.com/thinkdevcode/jQuery-Mustache 5 | // Dependancy - https://github.com/janl/mustache.js/ 6 | // Contact - gin4lyfe@gmail.com (Eugene Alfonso) 7 | // 8 | // See License.txt for full license 9 | // 10 | // Copyright (c) 2011 Eugene Alfonso, 11 | // Licensed under the MIT license. 12 | 13 | (function ($) { 14 | $.fn.mustache = function (data, partial, stream) { 15 | if (Mustache && data) { 16 | return $(Mustache.to_html(this.html(), data, partial, stream)); 17 | } 18 | }; 19 | })(jQuery); -------------------------------------------------------------------------------- /mustache.js: -------------------------------------------------------------------------------- 1 | /* 2 | mustache.js — Logic-less templates in JavaScript 3 | 4 | See http://mustache.github.com/ for more info. 5 | */ 6 | 7 | var Mustache = function() { 8 | var Renderer = function() {}; 9 | 10 | Renderer.prototype = { 11 | otag: "{{", 12 | ctag: "}}", 13 | pragmas: {}, 14 | buffer: [], 15 | pragmas_implemented: { 16 | "IMPLICIT-ITERATOR": true 17 | }, 18 | context: {}, 19 | 20 | render: function(template, context, partials, in_recursion) { 21 | // reset buffer & set context 22 | if(!in_recursion) { 23 | this.context = context; 24 | this.buffer = []; // TODO: make this non-lazy 25 | } 26 | 27 | // fail fast 28 | if(!this.includes("", template)) { 29 | if(in_recursion) { 30 | return template; 31 | } else { 32 | this.send(template); 33 | return; 34 | } 35 | } 36 | 37 | template = this.render_pragmas(template); 38 | var html = this.render_section(template, context, partials); 39 | if(in_recursion) { 40 | return this.render_tags(html, context, partials, in_recursion); 41 | } 42 | 43 | this.render_tags(html, context, partials, in_recursion); 44 | }, 45 | 46 | /* 47 | Sends parsed lines 48 | */ 49 | send: function(line) { 50 | if(line != "") { 51 | this.buffer.push(line); 52 | } 53 | }, 54 | 55 | /* 56 | Looks for %PRAGMAS 57 | */ 58 | render_pragmas: function(template) { 59 | // no pragmas 60 | if(!this.includes("%", template)) { 61 | return template; 62 | } 63 | 64 | var that = this; 65 | var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + 66 | this.ctag); 67 | return template.replace(regex, function(match, pragma, options) { 68 | if(!that.pragmas_implemented[pragma]) { 69 | throw({message: 70 | "This implementation of mustache doesn't understand the '" + 71 | pragma + "' pragma"}); 72 | } 73 | that.pragmas[pragma] = {}; 74 | if(options) { 75 | var opts = options.split("="); 76 | that.pragmas[pragma][opts[0]] = opts[1]; 77 | } 78 | return ""; 79 | // ignore unknown pragmas silently 80 | }); 81 | }, 82 | 83 | /* 84 | Tries to find a partial in the curent scope and render it 85 | */ 86 | render_partial: function(name, context, partials) { 87 | name = this.trim(name); 88 | if(!partials || partials[name] === undefined) { 89 | throw({message: "unknown_partial '" + name + "'"}); 90 | } 91 | if(typeof(context[name]) != "object") { 92 | return this.render(partials[name], context, partials, true); 93 | } 94 | return this.render(partials[name], context[name], partials, true); 95 | }, 96 | 97 | /* 98 | Renders inverted (^) and normal (#) sections 99 | */ 100 | render_section: function(template, context, partials) { 101 | if(!this.includes("#", template) && !this.includes("^", template)) { 102 | return template; 103 | } 104 | 105 | var that = this; 106 | // CSW - Added "+?" so it finds the tighest bound, not the widest 107 | var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + 108 | "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + 109 | "\\s*", "mg"); 110 | 111 | // for each {{#foo}}{{/foo}} section do... 112 | return template.replace(regex, function(match, type, name, content) { 113 | var value = that.find(name, context); 114 | if(type == "^") { // inverted section 115 | if(!value || that.is_array(value) && value.length === 0) { 116 | // false or empty list, render it 117 | return that.render(content, context, partials, true); 118 | } else { 119 | return ""; 120 | } 121 | } else if(type == "#") { // normal section 122 | if(that.is_array(value)) { // Enumerable, Let's loop! 123 | return that.map(value, function(row) { 124 | return that.render(content, that.create_context(row), 125 | partials, true); 126 | }).join(""); 127 | } else if(that.is_object(value)) { // Object, Use it as subcontext! 128 | return that.render(content, that.create_context(value), 129 | partials, true); 130 | } else if(typeof value === "function") { 131 | // higher order section 132 | return value.call(context, content, function(text) { 133 | return that.render(text, context, partials, true); 134 | }); 135 | } else if(value) { // boolean section 136 | return that.render(content, context, partials, true); 137 | } else { 138 | return ""; 139 | } 140 | } 141 | }); 142 | }, 143 | 144 | /* 145 | Replace {{foo}} and friends with values from our view 146 | */ 147 | render_tags: function(template, context, partials, in_recursion) { 148 | // tit for tat 149 | var that = this; 150 | 151 | var new_regex = function() { 152 | return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + 153 | that.ctag + "+", "g"); 154 | }; 155 | 156 | var regex = new_regex(); 157 | var tag_replace_callback = function(match, operator, name) { 158 | switch(operator) { 159 | case "!": // ignore comments 160 | return ""; 161 | case "=": // set new delimiters, rebuild the replace regexp 162 | that.set_delimiters(name); 163 | regex = new_regex(); 164 | return ""; 165 | case ">": // render partial 166 | return that.render_partial(name, context, partials); 167 | case "{": // the triple mustache is unescaped 168 | return that.find(name, context); 169 | default: // escape the value 170 | return that.escape(that.find(name, context)); 171 | } 172 | }; 173 | var lines = template.split("\n"); 174 | for(var i = 0; i < lines.length; i++) { 175 | lines[i] = lines[i].replace(regex, tag_replace_callback, this); 176 | if(!in_recursion) { 177 | this.send(lines[i]); 178 | } 179 | } 180 | 181 | if(in_recursion) { 182 | return lines.join("\n"); 183 | } 184 | }, 185 | 186 | set_delimiters: function(delimiters) { 187 | var dels = delimiters.split(" "); 188 | this.otag = this.escape_regex(dels[0]); 189 | this.ctag = this.escape_regex(dels[1]); 190 | }, 191 | 192 | escape_regex: function(text) { 193 | // thank you Simon Willison 194 | if(!arguments.callee.sRE) { 195 | var specials = [ 196 | '/', '.', '*', '+', '?', '|', 197 | '(', ')', '[', ']', '{', '}', '\\' 198 | ]; 199 | arguments.callee.sRE = new RegExp( 200 | '(\\' + specials.join('|\\') + ')', 'g' 201 | ); 202 | } 203 | return text.replace(arguments.callee.sRE, '\\$1'); 204 | }, 205 | 206 | /* 207 | find `name` in current `context`. That is find me a value 208 | from the view object 209 | */ 210 | find: function(name, context) { 211 | name = this.trim(name); 212 | 213 | // Checks whether a value is thruthy or false or 0 214 | function is_kinda_truthy(bool) { 215 | return bool === false || bool === 0 || bool; 216 | } 217 | 218 | var value; 219 | if(is_kinda_truthy(context[name])) { 220 | value = context[name]; 221 | } else if(is_kinda_truthy(this.context[name])) { 222 | value = this.context[name]; 223 | } 224 | 225 | if(typeof value === "function") { 226 | return value.apply(context); 227 | } 228 | if(value !== undefined) { 229 | return value; 230 | } 231 | // silently ignore unkown variables 232 | return ""; 233 | }, 234 | 235 | // Utility methods 236 | 237 | /* includes tag */ 238 | includes: function(needle, haystack) { 239 | return haystack.indexOf(this.otag + needle) != -1; 240 | }, 241 | 242 | /* 243 | Does away with nasty characters 244 | */ 245 | escape: function(s) { 246 | s = String(s === null ? "" : s); 247 | return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { 248 | switch(s) { 249 | case "&": return "&"; 250 | case "\\": return "\\\\"; 251 | case '"': return '"'; 252 | case "'": return '''; 253 | case "<": return "<"; 254 | case ">": return ">"; 255 | default: return s; 256 | } 257 | }); 258 | }, 259 | 260 | // by @langalex, support for arrays of strings 261 | create_context: function(_context) { 262 | if(this.is_object(_context)) { 263 | return _context; 264 | } else { 265 | var iterator = "."; 266 | if(this.pragmas["IMPLICIT-ITERATOR"]) { 267 | iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; 268 | } 269 | var ctx = {}; 270 | ctx[iterator] = _context; 271 | return ctx; 272 | } 273 | }, 274 | 275 | is_object: function(a) { 276 | return a && typeof a == "object"; 277 | }, 278 | 279 | is_array: function(a) { 280 | return Object.prototype.toString.call(a) === '[object Array]'; 281 | }, 282 | 283 | /* 284 | Gets rid of leading and trailing whitespace 285 | */ 286 | trim: function(s) { 287 | return s.replace(/^\s*|\s*$/g, ""); 288 | }, 289 | 290 | /* 291 | Why, why, why? Because IE. Cry, cry cry. 292 | */ 293 | map: function(array, fn) { 294 | if (typeof array.map == "function") { 295 | return array.map(fn); 296 | } else { 297 | var r = []; 298 | var l = array.length; 299 | for(var i = 0; i < l; i++) { 300 | r.push(fn(array[i])); 301 | } 302 | return r; 303 | } 304 | } 305 | }; 306 | 307 | return({ 308 | name: "mustache.js", 309 | version: "0.3.1-dev", 310 | 311 | /* 312 | Turns a template and view into HTML 313 | */ 314 | to_html: function(template, view, partials, send_fun) { 315 | var renderer = new Renderer(); 316 | if(send_fun) { 317 | renderer.send = send_fun; 318 | } 319 | renderer.render(template, view, partials); 320 | if(!send_fun) { 321 | return renderer.buffer.join("\n"); 322 | } 323 | } 324 | }); 325 | }(); 326 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |