You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
7.8KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = function() {
  6. var Renderer = function() {};
  7. Renderer.prototype = {
  8. otag: "{{",
  9. ctag: "}}",
  10. pragmas: {},
  11. buffer: [],
  12. pragmas_implemented: {
  13. "IMPLICIT-ITERATOR": true
  14. },
  15. render: function(template, context, partials, in_recursion) {
  16. // fail fast
  17. if(template.indexOf(this.otag) == -1) {
  18. if(in_recursion) {
  19. return template;
  20. } else {
  21. this.send(template);
  22. return;
  23. }
  24. }
  25. if(!in_recursion) {
  26. this.buffer = [];
  27. }
  28. template = this.render_pragmas(template);
  29. var html = this.render_section(template, context, partials);
  30. if(in_recursion) {
  31. return this.render_tags(html, context, partials, in_recursion);
  32. }
  33. this.render_tags(html, context, partials, in_recursion);
  34. },
  35. /*
  36. Sends parsed lines
  37. */
  38. send: function(line) {
  39. if(line != "") {
  40. this.buffer.push(line);
  41. }
  42. },
  43. /*
  44. Looks for %PRAGMAS
  45. */
  46. render_pragmas: function(template) {
  47. // no pragmas
  48. if(template.indexOf(this.otag + "%") == -1) {
  49. return template;
  50. }
  51. var that = this;
  52. var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
  53. + this.ctag);
  54. return template.replace(regex, function(match, pragma, options) {
  55. if(!that.pragmas_implemented[pragma]) {
  56. throw({message: "This implementation of mustache doesn't understand the '"
  57. + pragma + "' pragma"});
  58. }
  59. that.pragmas[pragma] = {};
  60. if(options) {
  61. var opts = options.split("=");
  62. that.pragmas[pragma][opts[0]] = opts[1];
  63. }
  64. return "";
  65. // ignore unknown pragmas silently
  66. });
  67. },
  68. /*
  69. Tries to find a partial in the global scope and render it
  70. */
  71. render_partial: function(name, context, partials) {
  72. if(!partials || !partials[name]) {
  73. throw({message: "unknown_partial '" + name + "'"});
  74. }
  75. if(typeof(context[name]) != "object") {
  76. return partials[name];
  77. }
  78. return this.render(partials[name], context[name], partials, true);
  79. },
  80. /*
  81. Renders boolean and enumerable sections
  82. */
  83. render_section: function(template, context, partials) {
  84. if(template.indexOf(this.otag + "#") == -1) {
  85. return template;
  86. }
  87. var that = this;
  88. // CSW - Added "+?" so it finds the tighest bound, not the widest
  89. var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag +
  90. "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
  91. // for each {{#foo}}{{/foo}} section do...
  92. return template.replace(regex, function(match, name, content) {
  93. var value = that.find(name, context);
  94. if(that.is_array(value)) { // Enumerable, Let's loop!
  95. return that.map(value, function(row) {
  96. return that.render(content, that.merge(context,
  97. that.create_context(row)), partials, true);
  98. }).join("");
  99. } else if(value) { // boolean section
  100. return that.render(content, context, partials, true);
  101. } else {
  102. return "";
  103. }
  104. });
  105. },
  106. /*
  107. Replace {{foo}} and friends with values from our view
  108. */
  109. render_tags: function(template, context, partials, in_recursion) {
  110. // tit for tat
  111. var that = this;
  112. var new_regex = function() {
  113. return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" +
  114. that.ctag + "+", "g");
  115. };
  116. var regex = new_regex();
  117. var lines = template.split("\n");
  118. for (var i=0; i < lines.length; i++) {
  119. lines[i] = lines[i].replace(regex, function(match, operator, name) {
  120. switch(operator) {
  121. case "!": // ignore comments
  122. return match;
  123. case "=": // set new delimiters, rebuild the replace regexp
  124. that.set_delimiters(name);
  125. regex = new_regex();
  126. return "";
  127. case ">": // render partial
  128. return that.render_partial(name, context, partials);
  129. case "{": // the triple mustache is unescaped
  130. return that.find(name, context);
  131. default: // escape the value
  132. return that.escape(that.find(name, context));
  133. }
  134. }, this);
  135. if(!in_recursion) {
  136. this.send(lines[i]);
  137. }
  138. }
  139. if(in_recursion) {
  140. return lines.join("\n");
  141. }
  142. },
  143. set_delimiters: function(delimiters) {
  144. var dels = delimiters.split(" ");
  145. this.otag = this.escape_regex(dels[0]);
  146. this.ctag = this.escape_regex(dels[1]);
  147. },
  148. escape_regex: function(text) {
  149. // thank you Simon Willison
  150. if(!arguments.callee.sRE) {
  151. var specials = [
  152. '/', '.', '*', '+', '?', '|',
  153. '(', ')', '[', ']', '{', '}', '\\'
  154. ];
  155. arguments.callee.sRE = new RegExp(
  156. '(\\' + specials.join('|\\') + ')', 'g'
  157. );
  158. }
  159. return text.replace(arguments.callee.sRE, '\\$1');
  160. },
  161. /*
  162. find `name` in current `context`. That is find me a value
  163. from the view object
  164. */
  165. find: function(name, context) {
  166. name = this.trim(name);
  167. if(typeof context[name] === "function") {
  168. return context[name].apply(context);
  169. }
  170. if(context[name] !== undefined) {
  171. return context[name];
  172. }
  173. // silently ignore unkown variables
  174. return "";
  175. },
  176. // Utility methods
  177. /*
  178. Does away with nasty characters
  179. */
  180. escape: function(s) {
  181. return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) {
  182. switch(s) {
  183. case "&": return "&amp;";
  184. case "\\": return "\\\\";;
  185. case '"': return '\"';;
  186. case "<": return "&lt;";
  187. case ">": return "&gt;";
  188. default: return s;
  189. }
  190. });
  191. },
  192. /*
  193. Merges all properties of object `b` into object `a`.
  194. `b.property` overwrites a.property`
  195. */
  196. merge: function(a, b) {
  197. var _new = {};
  198. for(var name in a) {
  199. if(a.hasOwnProperty(name)) {
  200. _new[name] = a[name];
  201. }
  202. };
  203. for(var name in b) {
  204. if(b.hasOwnProperty(name)) {
  205. _new[name] = b[name];
  206. }
  207. };
  208. return _new;
  209. },
  210. // by @langalex, support for arrays of strings
  211. create_context: function(_context) {
  212. if(this.is_object(_context)) {
  213. return _context;
  214. } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
  215. var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
  216. var ctx = {};
  217. ctx[iterator] = _context;
  218. return ctx;
  219. }
  220. },
  221. is_object: function(a) {
  222. return a && typeof a == "object";
  223. },
  224. is_array: function(a) {
  225. return Object.prototype.toString.call(a) === '[object Array]';
  226. },
  227. /*
  228. Gets rid of leading and trailing whitespace
  229. */
  230. trim: function(s) {
  231. return s.replace(/^\s*|\s*$/g, "");
  232. },
  233. /*
  234. Why, why, why? Because IE. Cry, cry cry.
  235. */
  236. map: function(array, fn) {
  237. if (typeof array.map == "function") {
  238. return array.map(fn);
  239. } else {
  240. var r = [];
  241. var l = array.length;
  242. for(var i=0;i<l;i++) {
  243. r.push(fn(array[i]));
  244. }
  245. return r;
  246. }
  247. }
  248. };
  249. return({
  250. name: "mustache.js",
  251. version: "0.2.3",
  252. /*
  253. Turns a template and view into HTML
  254. */
  255. to_html: function(template, view, partials, send_fun) {
  256. var renderer = new Renderer();
  257. if(send_fun) {
  258. renderer.send = send_fun;
  259. }
  260. renderer.render(template, view, partials);
  261. if(!send_fun) {
  262. return renderer.buffer.join("\n");
  263. }
  264. }
  265. });
  266. }();