Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

293 řádky
7.8KB

  1. /*
  2. Shamless port of http://github.com/defunkt/mustache
  3. by Jan Lehnardt <jan@apache.org>,
  4. Alexander Lang <alex@upstream-berlin.com>,
  5. Sebastian Cohnen <sebastian.cohnen@googlemail.com>
  6. Thanks @defunkt for the awesome code.
  7. See http://github.com/defunkt/mustache for more info.
  8. */
  9. var Mustache = function() {
  10. var Renderer = function() {};
  11. Renderer.prototype = {
  12. otag: "{{",
  13. ctag: "}}",
  14. pragmas: {},
  15. buffer: [],
  16. pragmas_parsed: false,
  17. render: function(template, context, partials, in_recursion) {
  18. // fail fast
  19. if(template.indexOf(this.otag) == -1) {
  20. if(in_recursion) {
  21. return template;
  22. } else {
  23. this.send(template);
  24. }
  25. }
  26. if(!in_recursion) {
  27. this.buffer = [];
  28. }
  29. if(!this.pragmas_parsed) {
  30. template = this.render_pragmas(template);
  31. }
  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. this.pragmas_parsed = true;
  51. // no pragmas
  52. if(template.indexOf(this.otag + "%") == -1) {
  53. return template;
  54. }
  55. var that = this;
  56. var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
  57. + this.ctag);
  58. return template.replace(regex, function(match, pragma, options) {
  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(typeof(context[name]) != "object") {
  73. throw({message: "subcontext for '" + name + "' is not an object"});
  74. }
  75. if(!partials || !partials[name]) {
  76. throw({message: "unknown_partial '" + 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. return lines.join("\n");
  140. },
  141. set_delimiters: function(delimiters) {
  142. var dels = delimiters.split(" ");
  143. this.otag = this.escape_regex(dels[0]);
  144. this.ctag = this.escape_regex(dels[1]);
  145. },
  146. escape_regex: function(text) {
  147. // thank you Simon Willison
  148. if(!arguments.callee.sRE) {
  149. var specials = [
  150. '/', '.', '*', '+', '?', '|',
  151. '(', ')', '[', ']', '{', '}', '\\'
  152. ];
  153. arguments.callee.sRE = new RegExp(
  154. '(\\' + specials.join('|\\') + ')', 'g'
  155. );
  156. }
  157. return text.replace(arguments.callee.sRE, '\\$1');
  158. },
  159. /*
  160. find `name` in current `context`. That is find me a value
  161. from the view object
  162. */
  163. find: function(name, context) {
  164. name = this.trim(name);
  165. if(typeof context[name] === "function") {
  166. return context[name].apply(context);
  167. }
  168. if(context[name] !== undefined) {
  169. return context[name];
  170. }
  171. // silently ignore unkown variables
  172. return "";
  173. },
  174. // Utility methods
  175. /*
  176. Does away with nasty characters
  177. */
  178. escape: function(s) {
  179. return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) {
  180. switch(s) {
  181. case "&": return "&amp;";
  182. case "\\": return "\\\\";;
  183. case '"': return '\"';;
  184. case "<": return "&lt;";
  185. case ">": return "&gt;";
  186. default: return s;
  187. }
  188. });
  189. },
  190. /*
  191. Merges all properties of object `b` into object `a`.
  192. `b.property` overwrites a.property`
  193. */
  194. merge: function(a, b) {
  195. var _new = {};
  196. for(var name in a) {
  197. if(a.hasOwnProperty(name)) {
  198. _new[name] = a[name];
  199. }
  200. };
  201. for(var name in b) {
  202. if(b.hasOwnProperty(name)) {
  203. _new[name] = b[name];
  204. }
  205. };
  206. return _new;
  207. },
  208. // by @langalex, support for arrays of strings
  209. create_context: function(_context) {
  210. if(this.is_object(_context)) {
  211. return _context;
  212. } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
  213. var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
  214. var ctx = {};
  215. ctx[iterator] = _context
  216. return ctx;
  217. }
  218. },
  219. is_object: function(a) {
  220. return a && typeof a == "object";
  221. },
  222. is_array: function(a) {
  223. return Object.prototype.toString.call(a) === '[object Array]';
  224. },
  225. /*
  226. Gets rid of leading and trailing whitespace
  227. */
  228. trim: function(s) {
  229. return s.replace(/^\s*|\s*$/g, "");
  230. },
  231. /*
  232. Why, why, why? Because IE. Cry, cry cry.
  233. */
  234. map: function(array, fn) {
  235. if (typeof array.map == "function") {
  236. return array.map(fn)
  237. } else {
  238. var r = [];
  239. var l = array.length;
  240. for(i=0;i<l;i++) {
  241. r.push(fn(array[i]));
  242. }
  243. return r;
  244. }
  245. }
  246. };
  247. return({
  248. name: "mustache.js",
  249. version: "0.2.3-dev",
  250. /*
  251. Turns a template and view into HTML
  252. */
  253. to_html: function(template, view, partials, send_fun) {
  254. var renderer = new Renderer();
  255. if(send_fun) {
  256. renderer.send = send_fun;
  257. }
  258. renderer.render(template, view, partials);
  259. return renderer.buffer.join("\n");
  260. }
  261. });
  262. }();