Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

291 wiersze
7.8KB

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