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

319 строки
8.8KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = function() {
  6. var Renderer = function() {};
  7. Renderer.prototype = {
  8. otag: "{{",
  9. ctag: "}}",
  10. pragmas: {},
  11. buffer: [],
  12. pragmas_implemented: {
  13. "IMPLICIT-ITERATOR": true
  14. },
  15. context: {},
  16. render: function(template, context, partials, in_recursion) {
  17. // reset buffer & set context
  18. if(!in_recursion) {
  19. this.context = context;
  20. this.buffer = []; // TODO: make this non-lazy
  21. }
  22. // fail fast
  23. if(!this.includes("", template)) {
  24. if(in_recursion) {
  25. return template;
  26. } else {
  27. this.send(template);
  28. return;
  29. }
  30. }
  31. template = this.render_pragmas(template);
  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. // no pragmas
  51. if(!this.includes("%", template)) {
  52. return template;
  53. }
  54. var that = this;
  55. var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
  56. + this.ctag);
  57. return template.replace(regex, function(match, pragma, options) {
  58. if(!that.pragmas_implemented[pragma]) {
  59. throw({message: "This implementation of mustache doesn't understand the '"
  60. + pragma + "' pragma"});
  61. }
  62. that.pragmas[pragma] = {};
  63. if(options) {
  64. var opts = options.split("=");
  65. that.pragmas[pragma][opts[0]] = opts[1];
  66. }
  67. return "";
  68. // ignore unknown pragmas silently
  69. });
  70. },
  71. /*
  72. Tries to find a partial in the curent scope and render it
  73. */
  74. render_partial: function(name, context, partials) {
  75. name = this.trim(name);
  76. if(!partials || !partials[name]) {
  77. throw({message: "unknown_partial '" + name + "'"});
  78. }
  79. if(typeof(context[name]) != "object") {
  80. return this.render(partials[name], context, partials, true);
  81. }
  82. return this.render(partials[name], context[name], partials, true);
  83. },
  84. /*
  85. Renders inverted (^) and normal (#) sections
  86. */
  87. render_section: function(template, context, partials) {
  88. if(!this.includes("#", template) && !this.includes("^", template)) {
  89. return template;
  90. }
  91. var that = this;
  92. // CSW - Added "+?" so it finds the tighest bound, not the widest
  93. var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
  94. "\\s*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
  95. "\\s*", "mg");
  96. // for each {{#foo}}{{/foo}} section do...
  97. return template.replace(regex, function(match, type, name, content) {
  98. var value = that.find(name, context);
  99. if(type == "^") { // inverted section
  100. if(!value || that.is_array(value) && value.length == 0) {
  101. // false or empty list, render it
  102. return that.render(content, context, partials, true);
  103. } else {
  104. return "";
  105. }
  106. } else if(type == "#") { // normal section
  107. if(that.is_array(value)) { // Enumerable, Let's loop!
  108. return that.map(value, function(row) {
  109. return that.render(content, that.create_context(row),
  110. partials, true);
  111. }).join("");
  112. } else if(that.is_object(value)) { // Object, Use it as subcontext!
  113. return that.render(content, that.create_context(value),
  114. partials, true);
  115. } else if(typeof value === "function") {
  116. // higher order section
  117. return value.call(context, content, function(text) {
  118. return that.render(text, context, partials, true);
  119. });
  120. } else if(value) { // boolean section
  121. return that.render(content, context, partials, true);
  122. } else {
  123. return "";
  124. }
  125. }
  126. });
  127. },
  128. /*
  129. Replace {{foo}} and friends with values from our view
  130. */
  131. render_tags: function(template, context, partials, in_recursion) {
  132. // tit for tat
  133. var that = this;
  134. var new_regex = function() {
  135. return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#\^]+?)\\1?" +
  136. that.ctag + "+", "g");
  137. };
  138. var regex = new_regex();
  139. var lines = template.split("\n");
  140. for (var i=0; i < lines.length; i++) {
  141. lines[i] = lines[i].replace(regex, function(match, operator, name) {
  142. switch(operator) {
  143. case "!": // ignore comments
  144. return "";
  145. case "=": // set new delimiters, rebuild the replace regexp
  146. that.set_delimiters(name);
  147. regex = new_regex();
  148. return "";
  149. case ">": // render partial
  150. return that.render_partial(name, context, partials);
  151. case "{": // the triple mustache is unescaped
  152. return that.find(name, context);
  153. default: // escape the value
  154. return that.escape(that.find(name, context));
  155. }
  156. }, this);
  157. if(!in_recursion) {
  158. this.send(lines[i]);
  159. }
  160. }
  161. if(in_recursion) {
  162. return lines.join("\n");
  163. }
  164. },
  165. set_delimiters: function(delimiters) {
  166. var dels = delimiters.split(" ");
  167. this.otag = this.escape_regex(dels[0]);
  168. this.ctag = this.escape_regex(dels[1]);
  169. },
  170. escape_regex: function(text) {
  171. // thank you Simon Willison
  172. if(!arguments.callee.sRE) {
  173. var specials = [
  174. '/', '.', '*', '+', '?', '|',
  175. '(', ')', '[', ']', '{', '}', '\\'
  176. ];
  177. arguments.callee.sRE = new RegExp(
  178. '(\\' + specials.join('|\\') + ')', 'g'
  179. );
  180. }
  181. return text.replace(arguments.callee.sRE, '\\$1');
  182. },
  183. /*
  184. find `name` in current `context`. That is find me a value
  185. from the view object
  186. */
  187. find: function(name, context) {
  188. name = this.trim(name);
  189. // Checks whether a value is thruthy or false or 0
  190. function is_kinda_truthy(bool) {
  191. return bool === false || bool === 0 || bool;
  192. }
  193. if(is_kinda_truthy(context[name])) {
  194. var value = context[name];
  195. } else if(is_kinda_truthy(this.context[name])) {
  196. var value = this.context[name];
  197. }
  198. if(typeof value === "function") {
  199. return value.apply(context);
  200. }
  201. if(value !== undefined) {
  202. return value;
  203. }
  204. // silently ignore unkown variables
  205. return "";
  206. },
  207. // Utility methods
  208. /* includes tag */
  209. includes: function(needle, haystack) {
  210. return haystack.indexOf(this.otag + needle) != -1;
  211. },
  212. /*
  213. Does away with nasty characters
  214. */
  215. escape: function(s) {
  216. s = String(s == null ? "" : s);
  217. return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
  218. switch(s) {
  219. case "&": return "&amp;";
  220. case "\\": return "\\\\";;
  221. case '"': return '\"';;
  222. case "<": return "&lt;";
  223. case ">": return "&gt;";
  224. default: return s;
  225. }
  226. });
  227. },
  228. // by @langalex, support for arrays of strings
  229. create_context: function(_context) {
  230. if(this.is_object(_context)) {
  231. return _context;
  232. } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
  233. var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
  234. var ctx = {};
  235. ctx[iterator] = _context;
  236. return ctx;
  237. }
  238. },
  239. is_object: function(a) {
  240. return a && typeof a == "object";
  241. },
  242. is_array: function(a) {
  243. return Object.prototype.toString.call(a) === '[object Array]';
  244. },
  245. /*
  246. Gets rid of leading and trailing whitespace
  247. */
  248. trim: function(s) {
  249. return s.replace(/^\s*|\s*$/g, "");
  250. },
  251. /*
  252. Why, why, why? Because IE. Cry, cry cry.
  253. */
  254. map: function(array, fn) {
  255. if (typeof array.map == "function") {
  256. return array.map(fn);
  257. } else {
  258. var r = [];
  259. var l = array.length;
  260. for(var i=0;i<l;i++) {
  261. r.push(fn(array[i]));
  262. }
  263. return r;
  264. }
  265. }
  266. };
  267. return({
  268. name: "mustache.js",
  269. version: "0.3.0-dev",
  270. /*
  271. Turns a template and view into HTML
  272. */
  273. to_html: function(template, view, partials, send_fun) {
  274. var renderer = new Renderer();
  275. if(send_fun) {
  276. renderer.send = send_fun;
  277. }
  278. renderer.render(template, view, partials);
  279. if(!send_fun) {
  280. return renderer.buffer.join("\n");
  281. }
  282. }
  283. });
  284. }();