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.

370 wiersze
10KB

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