Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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