Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

341 Zeilen
9.5KB

  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:
  60. "This implementation of mustache doesn't understand the '" +
  61. pragma + "' pragma"});
  62. }
  63. that.pragmas[pragma] = {};
  64. if(options) {
  65. var opts = options.split("=");
  66. that.pragmas[pragma][opts[0]] = opts[1];
  67. }
  68. return "";
  69. // ignore unknown pragmas silently
  70. });
  71. },
  72. /*
  73. Tries to find a partial in the current scope and render it
  74. */
  75. render_partial: function(name, context, partials) {
  76. name = this.trim(name);
  77. if(!partials || partials[name] === undefined) {
  78. throw({message: "unknown_partial '" + name + "'"});
  79. }
  80. if(typeof(context[name]) != "object") {
  81. return this.render(partials[name], context, partials, true);
  82. }
  83. return this.render(partials[name], context[name], partials, true);
  84. },
  85. /*
  86. Renders inverted (^) and normal (#) sections
  87. */
  88. render_section: function(template, context, partials) {
  89. if(!this.includes("#", template) && !this.includes("^", template)) {
  90. return template;
  91. }
  92. var that = this;
  93. // CSW - Added "+?" so it finds the tighest bound, not the widest
  94. var regex = new RegExp(this.otag + "(\\^|\\#)\\s*((.+?)(\\(.*\\))?)\\s*" + this.ctag +
  95. "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\3\\s*" + this.ctag +
  96. "\\s*", "mg");
  97. // for each {{#foo}}{{/foo}} section do...
  98. return template.replace(regex, function(match, type, name, reserved1, reserved2, content) {
  99. // the reserved variables are not being used
  100. var value = that.find(name, context);
  101. if(type == "^") { // inverted section
  102. if(!value || that.is_array(value) && value.length === 0) {
  103. // false or empty list, render it
  104. return that.render(content, context, partials, true);
  105. } else {
  106. return "";
  107. }
  108. } else if(type == "#") { // normal section
  109. if(that.is_array(value)) { // Enumerable, Let's loop!
  110. return that.map(value, function(row) {
  111. return that.render(content, that.create_context(row),
  112. partials, true);
  113. }).join("");
  114. } else if(that.is_object(value)) { // Object, Use it as subcontext!
  115. return that.render(content, that.create_context(value),
  116. partials, true);
  117. } else if(typeof value === "function") {
  118. // higher order section
  119. return value.call(context, content, function(text) {
  120. return that.render(text, context, partials, true);
  121. });
  122. } else if(value) { // boolean section
  123. return that.render(content, context, partials, true);
  124. } else {
  125. return "";
  126. }
  127. }
  128. });
  129. },
  130. /*
  131. Replace {{foo}} and friends with values from our view
  132. */
  133. render_tags: function(template, context, partials, in_recursion) {
  134. // tit for tat
  135. var that = this;
  136. var new_regex = function() {
  137. return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
  138. that.ctag + "+", "g");
  139. };
  140. var regex = new_regex();
  141. var tag_replace_callback = 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. };
  157. var lines = template.split("\n");
  158. for(var i = 0; i < lines.length; i++) {
  159. lines[i] = lines[i].replace(regex, tag_replace_callback, this);
  160. if(!in_recursion) {
  161. this.send(lines[i]);
  162. }
  163. }
  164. if(in_recursion) {
  165. return lines.join("\n");
  166. }
  167. },
  168. set_delimiters: function(delimiters) {
  169. var dels = delimiters.split(" ");
  170. this.otag = this.escape_regex(dels[0]);
  171. this.ctag = this.escape_regex(dels[1]);
  172. },
  173. escape_regex: function(text) {
  174. // thank you Simon Willison
  175. if(!arguments.callee.sRE) {
  176. var specials = [
  177. '/', '.', '*', '+', '?', '|',
  178. '(', ')', '[', ']', '{', '}', '\\'
  179. ];
  180. arguments.callee.sRE = new RegExp(
  181. '(\\' + specials.join('|\\') + ')', 'g'
  182. );
  183. }
  184. return text.replace(arguments.callee.sRE, '\\$1');
  185. },
  186. /*
  187. find `name` in current `context`. That is find me a value
  188. from the view object
  189. */
  190. find: function(name, context) {
  191. name = this.trim(name);
  192. // Checks whether a value is truthy or false or 0
  193. function is_kinda_truthy(bool) {
  194. return bool === false || bool === 0 || bool;
  195. }
  196. var value;
  197. if(is_kinda_truthy(context[name])) {
  198. value = context[name];
  199. } else if(is_kinda_truthy(this.context[name])) {
  200. value = this.context[name];
  201. }
  202. if(typeof value === "function") {
  203. return value.apply(context);
  204. }
  205. if(value !== undefined) {
  206. return value;
  207. }
  208. if (value === undefined && name.indexOf('(') != -1) {
  209. // if value turned out to not exist on the context, then check to see if the function is parameterized
  210. var matches = name.match(/(.+)\((.+,?)+\)/);
  211. if (matches.length === 3) {
  212. name = matches[1];
  213. value = this.context[name];
  214. if (typeof value === "function" && matches[2]) {
  215. var that = this;
  216. var args = this.map(matches[2].split(/\s*,/),
  217. function(ele) { return that.find(ele, that.context); });
  218. return value.apply(context, args);
  219. }
  220. }
  221. }
  222. // silently ignore unknown variables
  223. return "";
  224. },
  225. // Utility methods
  226. /* includes tag */
  227. includes: function(needle, haystack) {
  228. return haystack.indexOf(this.otag + needle) != -1;
  229. },
  230. /*
  231. Does away with nasty characters
  232. */
  233. escape: function(s) {
  234. s = String(s === null ? "" : s);
  235. return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
  236. switch(s) {
  237. case "&": return "&amp;";
  238. case "\\": return "\\\\";
  239. case '"': return '\"';
  240. case "<": return "&lt;";
  241. case ">": return "&gt;";
  242. default: return s;
  243. }
  244. });
  245. },
  246. // by @langalex, support for arrays of strings
  247. create_context: function(_context) {
  248. if(this.is_object(_context)) {
  249. return _context;
  250. } else {
  251. var iterator = ".";
  252. if(this.pragmas["IMPLICIT-ITERATOR"]) {
  253. iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
  254. }
  255. var ctx = {};
  256. ctx[iterator] = _context;
  257. return ctx;
  258. }
  259. },
  260. is_object: function(a) {
  261. return a && typeof a == "object";
  262. },
  263. is_array: function(a) {
  264. return Object.prototype.toString.call(a) === '[object Array]';
  265. },
  266. /*
  267. Gets rid of leading and trailing whitespace
  268. */
  269. trim: function(s) {
  270. return s.replace(/^\s*|\s*$/g, "");
  271. },
  272. /*
  273. Why, why, why? Because IE. Cry, cry cry.
  274. */
  275. map: function(array, fn) {
  276. if (typeof array.map == "function") {
  277. return array.map(fn);
  278. } else {
  279. var r = [];
  280. var l = array.length;
  281. for(var i = 0; i < l; i++) {
  282. r.push(fn(array[i]));
  283. }
  284. return r;
  285. }
  286. }
  287. };
  288. return({
  289. name: "mustache.js",
  290. version: "0.3.0-dev",
  291. /*
  292. Turns a template and view into HTML
  293. */
  294. to_html: function(template, view, partials, send_fun) {
  295. var renderer = new Renderer();
  296. if(send_fun) {
  297. renderer.send = send_fun;
  298. }
  299. renderer.render(template, view, partials);
  300. if(!send_fun) {
  301. return renderer.buffer.join("\n");
  302. }
  303. }
  304. });
  305. }();