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.

235 lines
5.9KB

  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. var new_result = "";
  34. var result = this.render_tags(html);
  35. // print(result);
  36. // while(result != new_result) {
  37. // print(1);
  38. // result = new_result;
  39. // new_result = this.render_tags(result);
  40. // print(new_result);
  41. // }
  42. return result;
  43. },
  44. /*
  45. Tries to find a partial in the global scope and render it
  46. */
  47. render_partial: function(name) {
  48. // FIXME: too hacky
  49. var evil_name = eval(name)
  50. switch(typeof evil_name) {
  51. case "string": // a tring partial, we simply render
  52. return this.to_html(evil_name, "");
  53. case "object": // a view partial needs a `name_template` template to render
  54. var tpl = name + "_template";
  55. return this.to_html(eval(tpl), evil_name);
  56. default: // should not happen #famouslastwords
  57. throw("Unknown partial type.");
  58. }
  59. },
  60. /*
  61. Renders boolean and enumerable sections
  62. */
  63. render_section: function(template) {
  64. if(template.indexOf(this.otag + "#") == -1) {
  65. return template;
  66. }
  67. var that = this;
  68. var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag + "\\s*([\\s\\S]+)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
  69. // for each {{#foo}}{{/foo}} section do...
  70. return template.replace(regex, function(match, name, content) {
  71. var value = that.find(name);
  72. if(that.is_array(value)) { // Enumerable, Let's loop!
  73. return value.map(function(row) {
  74. return that.render(content, row);
  75. }).join('');
  76. } else if(value) { // boolean section
  77. return that.render(content);
  78. } else {
  79. return "";
  80. }
  81. }
  82. );
  83. },
  84. /*
  85. Replace {{foo}} and friends with values from our view
  86. */
  87. render_tags: function(template) {
  88. // tit for tat
  89. var that = this;
  90. // for each {{(!<{)?foo}} tag, do...
  91. regex = this.new_regex();
  92. var lines = template;
  93. while(regex.test(lines)) {
  94. template = template.replace(regex, function (match, operator, name) {
  95. switch(operator) {
  96. case "!": // ignore comments
  97. return match;
  98. case "=": // set new delimiters
  99. that.set_delimiters(name);
  100. return "";
  101. case "<": // render partial
  102. return that.render_partial(name);
  103. case "{": // the triple mustache is unescaped
  104. return that.find(name);
  105. default: // escape the value
  106. // print("render name: '" +name+ "'");
  107. return that.escape(that.find(name));
  108. }
  109. }, this);
  110. regex = this.new_regex();
  111. lines = this.pop_first(lines);
  112. }
  113. return template;
  114. },
  115. pop_first: function(lines) {
  116. var lines_array = lines.split("\n");
  117. lines_array.shift();
  118. return lines_array.join("\n");
  119. },
  120. new_regex: function() {
  121. return new RegExp(this.otag + "(=|!|<|\\{)?([^\/#]+?)\\1?" + this.ctag + "+", "m");
  122. },
  123. /*
  124. {{=<% %>=}}
  125. * <% first %>
  126. <%=| |=%>
  127. * | second |
  128. |={{ }}=|
  129. * {{ third }}
  130. */
  131. set_delimiters: function(delimiters) {
  132. var dels = delimiters.split(" ");
  133. this.otag = this.escape_regex(dels[0]);
  134. this.ctag = this.escape_regex(dels[1]);
  135. },
  136. escape_regex: function(text) {
  137. // thank you Simon Willison
  138. if(!arguments.callee.sRE) {
  139. var specials = [
  140. '/', '.', '*', '+', '?', '|',
  141. '(', ')', '[', ']', '{', '}', '\\'
  142. ];
  143. arguments.callee.sRE = new RegExp(
  144. '(\\' + specials.join('|\\') + ')', 'g'
  145. );
  146. }
  147. return text.replace(arguments.callee.sRE, '\\$1');
  148. },
  149. /*
  150. find `name` in current `context`. That is find me a value
  151. from the view object
  152. */
  153. find: function(name) {
  154. name = this.trim(name);
  155. if(name === "") {
  156. // return "";
  157. }
  158. var context = this.context;
  159. if(typeof context[name] === "function") {
  160. return context[name].apply(context);
  161. }
  162. if(context[name] !== undefined) {
  163. return context[name];
  164. }
  165. throw("Can't find " + name + " in " + context);
  166. },
  167. // Utility methods
  168. /*
  169. Does away with nasty characters
  170. */
  171. escape: function(s) {
  172. return s.toString().replace(/[&"<>\\]/g, function(s) {
  173. switch(s) {
  174. case "&": return "&amp;";
  175. case "\\": return "\\\\";;
  176. case '"': return '\"';;
  177. case "<": return "&lt;";
  178. case ">": return "&gt;";
  179. default: return s;
  180. }
  181. });
  182. },
  183. /*
  184. Merges all properties of object `b` into object `a`.
  185. `b.property` overwrites a.property`
  186. */
  187. merge: function(a, b) {
  188. for(var name in b) {
  189. if(b.hasOwnProperty(name)) {
  190. a[name] = b[name];
  191. }
  192. }
  193. return a;
  194. },
  195. /*
  196. Thanks Doug Crockford
  197. JavaScript — The Good Parts lists an alternative that works better with
  198. frames. Frames can suck it, we use the simple version.
  199. */
  200. is_array: function(a) {
  201. return (a &&
  202. typeof a === 'object' &&
  203. a.constructor === Array);
  204. },
  205. /*
  206. Gets rid of leading and trailing whitespace
  207. */
  208. trim: function(s) {
  209. return s.replace(/^\s*|\s*$/g, '');
  210. },
  211. };