Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

283 строки
7.5KB

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