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.

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