No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

420 líneas
12KB

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