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.

219 lines
5.5KB

  1. /*
  2. Shamless port of http://github.com/defunkt/mustache
  3. by Jan Lehnardt <jan@apache.org>
  4. Thanks @defunkt for the awesome code.
  5. See http://github.com/defunkt/mustache for more info.
  6. */
  7. var Mustache = {
  8. name: "mustache.js",
  9. version: "0.1",
  10. context: {},
  11. otag: "{{",
  12. ctag: "}}",
  13. /*
  14. Public method. Turns a template and view into HTML
  15. */
  16. to_html: function(template, view) {
  17. return this.render(template, view);
  18. },
  19. // Private Methods
  20. render: function(template, view) {
  21. // fail fast
  22. if(template.indexOf(this.otag) == -1) {
  23. return template;
  24. }
  25. // keep context around for recursive calls
  26. this.context = context = this.merge((this.context || {}), view);
  27. // first, render all sections
  28. var html = this.render_section(template);
  29. // restore context, recursion might have messed it up
  30. this.context = context;
  31. // finally, render tags
  32. // render until all is rendered
  33. return this.render_tags(html);
  34. },
  35. /*
  36. Tries to find a partial in the global scope and render it
  37. */
  38. render_partial: function(name) {
  39. // FIXME: too hacky
  40. var evil_name = eval(name)
  41. switch(typeof evil_name) {
  42. case "string": // a tring partial, we simply render
  43. return this.to_html(evil_name, "");
  44. case "object": // a view partial needs a `name_template` template
  45. var tpl = name + "_template";
  46. return this.to_html(eval(tpl), evil_name);
  47. default: // should not happen #famouslastwords
  48. throw("Unknown partial type.");
  49. }
  50. },
  51. /*
  52. Renders boolean and enumerable sections
  53. */
  54. render_section: function(template) {
  55. if(template.indexOf(this.otag + "#") == -1) {
  56. return template;
  57. }
  58. var that = this;
  59. var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag +
  60. "\\s*([\\s\\S]+)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
  61. // for each {{#foo}}{{/foo}} section do...
  62. return template.replace(regex, function(match, name, content) {
  63. var value = that.find(name);
  64. if(that.is_array(value)) { // Enumerable, Let's loop!
  65. return value.map(function(row) {
  66. return that.render(content, row);
  67. }).join('');
  68. } else if(value) { // boolean section
  69. return that.render(content);
  70. } else {
  71. return "";
  72. }
  73. }
  74. );
  75. },
  76. /*
  77. Replace {{foo}} and friends with values from our view
  78. */
  79. render_tags: function(template) {
  80. var pop_first = function(lines) {
  81. var lines_array = lines.split("\n");
  82. lines_array.shift();
  83. return lines_array.join("\n");
  84. };
  85. var new_regex = function() {
  86. return new RegExp(that.otag +
  87. "(=|!|<|\\{)?([^\/#]+?)\\1?" + that.ctag + "+", "m");
  88. };
  89. // tit for tat
  90. var that = this;
  91. regex = new_regex();
  92. var lines = template;
  93. // for each {{(!<{)?foo}} tag, do...
  94. while(regex.test(lines)) {
  95. template = template.replace(regex, function (match, operator, name) {
  96. switch(operator) {
  97. case "!": // ignore comments
  98. return match;
  99. case "=": // set new delimiters
  100. that.set_delimiters(name);
  101. return "";
  102. case "<": // render partial
  103. return that.render_partial(name);
  104. case "{": // the triple mustache is unescaped
  105. return that.find(name);
  106. default: // escape the value
  107. return that.escape(that.find(name));
  108. }
  109. }, this);
  110. regex = new_regex();
  111. lines = pop_first(lines);
  112. }
  113. return template;
  114. },
  115. set_delimiters: function(delimiters) {
  116. var dels = delimiters.split(" ");
  117. this.otag = this.escape_regex(dels[0]);
  118. this.ctag = this.escape_regex(dels[1]);
  119. },
  120. escape_regex: function(text) {
  121. // thank you Simon Willison
  122. if(!arguments.callee.sRE) {
  123. var specials = [
  124. '/', '.', '*', '+', '?', '|',
  125. '(', ')', '[', ']', '{', '}', '\\'
  126. ];
  127. arguments.callee.sRE = new RegExp(
  128. '(\\' + specials.join('|\\') + ')', 'g'
  129. );
  130. }
  131. return text.replace(arguments.callee.sRE, '\\$1');
  132. },
  133. /*
  134. find `name` in current `context`. That is find me a value
  135. from the view object
  136. */
  137. find: function(name) {
  138. name = this.trim(name);
  139. var context = this.context;
  140. if(typeof context[name] === "function") {
  141. return context[name].apply(context);
  142. }
  143. if(context[name] !== undefined) {
  144. return context[name];
  145. }
  146. throw("Can't find " + name + " in " + context);
  147. },
  148. // Utility methods
  149. /*
  150. Does away with nasty characters
  151. */
  152. escape: function(s) {
  153. return s.toString().replace(/[&"<>\\]/g, function(s) {
  154. switch(s) {
  155. case "&": return "&amp;";
  156. case "\\": return "\\\\";;
  157. case '"': return '\"';;
  158. case "<": return "&lt;";
  159. case ">": return "&gt;";
  160. default: return s;
  161. }
  162. });
  163. },
  164. /*
  165. Merges all properties of object `b` into object `a`.
  166. `b.property` overwrites a.property`
  167. */
  168. merge: function(a, b) {
  169. for(var name in b) {
  170. if(b.hasOwnProperty(name)) {
  171. a[name] = b[name];
  172. }
  173. }
  174. return a;
  175. },
  176. /*
  177. Thanks Doug Crockford
  178. JavaScript — The Good Parts lists an alternative that works better with
  179. frames. Frames can suck it, we use the simple version.
  180. */
  181. is_array: function(a) {
  182. return (a &&
  183. typeof a === 'object' &&
  184. a.constructor === Array);
  185. },
  186. /*
  187. Gets rid of leading and trailing whitespace
  188. */
  189. trim: function(s) {
  190. return s.replace(/^\s*|\s*$/g, '');
  191. },
  192. };