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

372 строки
11KB

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