您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

560 行
15KB

  1. /*
  2. Shameless port of a shameless port
  3. @defunkt => @janl => @aq
  4. See http://github.com/defunkt/mustache for more info.
  5. */
  6. ;(function($) {
  7. /*!
  8. * mustache.js - Logic-less {{mustache}} templates with JavaScript
  9. * http://github.com/janl/mustache.js
  10. */
  11. var Mustache = (typeof module !== "undefined" && module.exports) || {};
  12. (function (exports) {
  13. exports.name = "mustache.js";
  14. exports.version = "0.5.14";
  15. exports.tags = ["{{", "}}"];
  16. exports.parse = parse;
  17. exports.compile = compile;
  18. exports.render = render;
  19. exports.clearCache = clearCache;
  20. // This is here for backwards compatibility with 0.4.x.
  21. exports.to_html = function (template, view, partials, send) {
  22. var result = render(template, view, partials);
  23. if (typeof send === "function") {
  24. send(result);
  25. } else {
  26. return result;
  27. }
  28. };
  29. var _toString = Object.prototype.toString;
  30. var _isArray = Array.isArray;
  31. var _forEach = Array.prototype.forEach;
  32. var _trim = String.prototype.trim;
  33. var isArray;
  34. if (_isArray) {
  35. isArray = _isArray;
  36. } else {
  37. isArray = function (obj) {
  38. return _toString.call(obj) === "[object Array]";
  39. };
  40. }
  41. var forEach;
  42. if (_forEach) {
  43. forEach = function (obj, callback, scope) {
  44. return _forEach.call(obj, callback, scope);
  45. };
  46. } else {
  47. forEach = function (obj, callback, scope) {
  48. for (var i = 0, len = obj.length; i < len; ++i) {
  49. callback.call(scope, obj[i], i, obj);
  50. }
  51. };
  52. }
  53. var spaceRe = /^\s*$/;
  54. function isWhitespace(string) {
  55. return spaceRe.test(string);
  56. }
  57. var trim;
  58. if (_trim) {
  59. trim = function (string) {
  60. return string == null ? "" : _trim.call(string);
  61. };
  62. } else {
  63. var trimLeft, trimRight;
  64. if (isWhitespace("\xA0")) {
  65. trimLeft = /^\s+/;
  66. trimRight = /\s+$/;
  67. } else {
  68. // IE doesn't match non-breaking spaces with \s, thanks jQuery.
  69. trimLeft = /^[\s\xA0]+/;
  70. trimRight = /[\s\xA0]+$/;
  71. }
  72. trim = function (string) {
  73. return string == null ? "" :
  74. String(string).replace(trimLeft, "").replace(trimRight, "");
  75. };
  76. }
  77. var escapeMap = {
  78. "&": "&amp;",
  79. "<": "&lt;",
  80. ">": "&gt;",
  81. '"': '&quot;',
  82. "'": '&#39;'
  83. };
  84. function escapeHTML(string) {
  85. return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
  86. return escapeMap[s] || s;
  87. });
  88. }
  89. /**
  90. * Adds the `template`, `line`, and `file` properties to the given error
  91. * object and alters the message to provide more useful debugging information.
  92. */
  93. function debug(e, template, line, file) {
  94. file = file || "<template>";
  95. var lines = template.split("\n"),
  96. start = Math.max(line - 3, 0),
  97. end = Math.min(lines.length, line + 3),
  98. context = lines.slice(start, end);
  99. var c;
  100. for (var i = 0, len = context.length; i < len; ++i) {
  101. c = i + start + 1;
  102. context[i] = (c === line ? " >> " : " ") + context[i];
  103. }
  104. e.template = template;
  105. e.line = line;
  106. e.file = file;
  107. e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
  108. return e;
  109. }
  110. /**
  111. * Looks up the value of the given `name` in the given context `stack`.
  112. */
  113. function lookup(name, stack, defaultValue) {
  114. if (name === ".") {
  115. return stack[stack.length - 1];
  116. }
  117. var names = name.split(".");
  118. var lastIndex = names.length - 1;
  119. var target = names[lastIndex];
  120. var value, context, i = stack.length, j, localStack;
  121. while (i) {
  122. localStack = stack.slice(0);
  123. context = stack[--i];
  124. j = 0;
  125. while (j < lastIndex) {
  126. context = context[names[j++]];
  127. if (context == null) {
  128. break;
  129. }
  130. localStack.push(context);
  131. }
  132. if (context && typeof context === "object" && target in context) {
  133. value = context[target];
  134. break;
  135. }
  136. }
  137. // If the value is a function, call it in the current context.
  138. if (typeof value === "function") {
  139. value = value.call(localStack[localStack.length - 1]);
  140. }
  141. if (value == null) {
  142. return defaultValue;
  143. }
  144. return value;
  145. }
  146. function renderSection(name, stack, callback, inverted) {
  147. var buffer = "";
  148. var value = lookup(name, stack);
  149. if (inverted) {
  150. // From the spec: inverted sections may render text once based on the
  151. // inverse value of the key. That is, they will be rendered if the key
  152. // doesn't exist, is false, or is an empty list.
  153. if (value == null || value === false || (isArray(value) && value.length === 0)) {
  154. buffer += callback();
  155. }
  156. } else if (isArray(value)) {
  157. forEach(value, function (value) {
  158. stack.push(value);
  159. buffer += callback();
  160. stack.pop();
  161. });
  162. } else if (typeof value === "object") {
  163. stack.push(value);
  164. buffer += callback();
  165. stack.pop();
  166. } else if (typeof value === "function") {
  167. var scope = stack[stack.length - 1];
  168. var scopedRender = function (template) {
  169. return render(template, scope);
  170. };
  171. buffer += value.call(scope, callback(), scopedRender) || "";
  172. } else if (value) {
  173. buffer += callback();
  174. }
  175. return buffer;
  176. }
  177. /**
  178. * Parses the given `template` and returns the source of a function that,
  179. * with the proper arguments, will render the template. Recognized options
  180. * include the following:
  181. *
  182. * - file The name of the file the template comes from (displayed in
  183. * error messages)
  184. * - tags An array of open and close tags the `template` uses. Defaults
  185. * to the value of Mustache.tags
  186. * - debug Set `true` to log the body of the generated function to the
  187. * console
  188. * - space Set `true` to preserve whitespace from lines that otherwise
  189. * contain only a {{tag}}. Defaults to `false`
  190. */
  191. function parse(template, options) {
  192. options = options || {};
  193. var tags = options.tags || exports.tags,
  194. openTag = tags[0],
  195. closeTag = tags[tags.length - 1];
  196. var code = [
  197. 'var buffer = "";', // output buffer
  198. "\nvar line = 1;", // keep track of source line number
  199. "\ntry {",
  200. '\nbuffer += "'
  201. ];
  202. var spaces = [], // indices of whitespace in code on the current line
  203. hasTag = false, // is there a {{tag}} on the current line?
  204. nonSpace = false; // is there a non-space char on the current line?
  205. // Strips all space characters from the code array for the current line
  206. // if there was a {{tag}} on it and otherwise only spaces.
  207. var stripSpace = function () {
  208. if (hasTag && !nonSpace && !options.space) {
  209. while (spaces.length) {
  210. code.splice(spaces.pop(), 1);
  211. }
  212. } else {
  213. spaces = [];
  214. }
  215. hasTag = false;
  216. nonSpace = false;
  217. };
  218. var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
  219. var setTags = function (source) {
  220. tags = trim(source).split(/\s+/);
  221. nextOpenTag = tags[0];
  222. nextCloseTag = tags[tags.length - 1];
  223. };
  224. var includePartial = function (source) {
  225. code.push(
  226. '";',
  227. updateLine,
  228. '\nvar partial = partials["' + trim(source) + '"];',
  229. '\nif (partial) {',
  230. '\n buffer += render(partial,stack[stack.length - 1],partials);',
  231. '\n}',
  232. '\nbuffer += "'
  233. );
  234. };
  235. var openSection = function (source, inverted) {
  236. var name = trim(source);
  237. if (name === "") {
  238. throw debug(new Error("Section name may not be empty"), template, line, options.file);
  239. }
  240. sectionStack.push({name: name, inverted: inverted});
  241. code.push(
  242. '";',
  243. updateLine,
  244. '\nvar name = "' + name + '";',
  245. '\nvar callback = (function () {',
  246. '\n return function () {',
  247. '\n var buffer = "";',
  248. '\nbuffer += "'
  249. );
  250. };
  251. var openInvertedSection = function (source) {
  252. openSection(source, true);
  253. };
  254. var closeSection = function (source) {
  255. var name = trim(source);
  256. var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
  257. if (!openName || name != openName) {
  258. throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
  259. }
  260. var section = sectionStack.pop();
  261. code.push(
  262. '";',
  263. '\n return buffer;',
  264. '\n };',
  265. '\n})();'
  266. );
  267. if (section.inverted) {
  268. code.push("\nbuffer += renderSection(name,stack,callback,true);");
  269. } else {
  270. code.push("\nbuffer += renderSection(name,stack,callback);");
  271. }
  272. code.push('\nbuffer += "');
  273. };
  274. var sendPlain = function (source) {
  275. code.push(
  276. '";',
  277. updateLine,
  278. '\nbuffer += lookup("' + trim(source) + '",stack,"");',
  279. '\nbuffer += "'
  280. );
  281. };
  282. var sendEscaped = function (source) {
  283. code.push(
  284. '";',
  285. updateLine,
  286. '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
  287. '\nbuffer += "'
  288. );
  289. };
  290. var line = 1, c, callback;
  291. for (var i = 0, len = template.length; i < len; ++i) {
  292. if (template.slice(i, i + openTag.length) === openTag) {
  293. i += openTag.length;
  294. c = template.substr(i, 1);
  295. updateLine = '\nline = ' + line + ';';
  296. nextOpenTag = openTag;
  297. nextCloseTag = closeTag;
  298. hasTag = true;
  299. switch (c) {
  300. case "!": // comment
  301. i++;
  302. callback = null;
  303. break;
  304. case "=": // change open/close tags, e.g. {{=<% %>=}}
  305. i++;
  306. closeTag = "=" + closeTag;
  307. callback = setTags;
  308. break;
  309. case ">": // include partial
  310. i++;
  311. callback = includePartial;
  312. break;
  313. case "#": // start section
  314. i++;
  315. callback = openSection;
  316. break;
  317. case "^": // start inverted section
  318. i++;
  319. callback = openInvertedSection;
  320. break;
  321. case "/": // end section
  322. i++;
  323. callback = closeSection;
  324. break;
  325. case "{": // plain variable
  326. closeTag = "}" + closeTag;
  327. // fall through
  328. case "&": // plain variable
  329. i++;
  330. nonSpace = true;
  331. callback = sendPlain;
  332. break;
  333. default: // escaped variable
  334. nonSpace = true;
  335. callback = sendEscaped;
  336. }
  337. var end = template.indexOf(closeTag, i);
  338. if (end === -1) {
  339. throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
  340. }
  341. var source = template.substring(i, end);
  342. if (callback) {
  343. callback(source);
  344. }
  345. // Maintain line count for \n in source.
  346. var n = 0;
  347. while (~(n = source.indexOf("\n", n))) {
  348. line++;
  349. n++;
  350. }
  351. i = end + closeTag.length - 1;
  352. openTag = nextOpenTag;
  353. closeTag = nextCloseTag;
  354. } else {
  355. c = template.substr(i, 1);
  356. switch (c) {
  357. case '"':
  358. case "\\":
  359. nonSpace = true;
  360. code.push("\\" + c);
  361. break;
  362. case "\r":
  363. // Ignore carriage returns.
  364. break;
  365. case "\n":
  366. spaces.push(code.length);
  367. code.push("\\n");
  368. stripSpace(); // Check for whitespace on the current line.
  369. line++;
  370. break;
  371. default:
  372. if (isWhitespace(c)) {
  373. spaces.push(code.length);
  374. } else {
  375. nonSpace = true;
  376. }
  377. code.push(c);
  378. }
  379. }
  380. }
  381. if (sectionStack.length != 0) {
  382. throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
  383. }
  384. // Clean up any whitespace from a closing {{tag}} that was at the end
  385. // of the template without a trailing \n.
  386. stripSpace();
  387. code.push(
  388. '";',
  389. "\nreturn buffer;",
  390. "\n} catch (e) { throw {error: e, line: line}; }"
  391. );
  392. // Ignore `buffer += "";` statements.
  393. var body = code.join("").replace(/buffer \+= "";\n/g, "");
  394. if (options.debug) {
  395. if (typeof console != "undefined" && console.log) {
  396. console.log(body);
  397. } else if (typeof print === "function") {
  398. print(body);
  399. }
  400. }
  401. return body;
  402. }
  403. /**
  404. * Used by `compile` to generate a reusable function for the given `template`.
  405. */
  406. function _compile(template, options) {
  407. var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
  408. var body = parse(template, options);
  409. var fn = new Function(args, body);
  410. // This anonymous function wraps the generated function so we can do
  411. // argument coercion, setup some variables, and handle any errors
  412. // encountered while executing it.
  413. return function (view, partials) {
  414. partials = partials || {};
  415. var stack = [view]; // context stack
  416. try {
  417. return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
  418. } catch (e) {
  419. throw debug(e.error, template, e.line, options.file);
  420. }
  421. };
  422. }
  423. // Cache of pre-compiled templates.
  424. var _cache = {};
  425. /**
  426. * Clear the cache of compiled templates.
  427. */
  428. function clearCache() {
  429. _cache = {};
  430. }
  431. /**
  432. * Compiles the given `template` into a reusable function using the given
  433. * `options`. In addition to the options accepted by Mustache.parse,
  434. * recognized options include the following:
  435. *
  436. * - cache Set `false` to bypass any pre-compiled version of the given
  437. * template. Otherwise, a given `template` string will be cached
  438. * the first time it is parsed
  439. */
  440. function compile(template, options) {
  441. options = options || {};
  442. // Use a pre-compiled version from the cache if we have one.
  443. if (options.cache !== false) {
  444. if (!_cache[template]) {
  445. _cache[template] = _compile(template, options);
  446. }
  447. return _cache[template];
  448. }
  449. return _compile(template, options);
  450. }
  451. /**
  452. * High-level function that renders the given `template` using the given
  453. * `view` and `partials`. If you need to use any of the template options (see
  454. * `compile` above), you must compile in a separate step, and then call that
  455. * compiled function.
  456. */
  457. function render(template, view, partials) {
  458. return compile(template)(view, partials);
  459. }
  460. })(Mustache);
  461. $.mustache = function (template, view, partials) {
  462. return Mustache.render(template, view, partials);
  463. };
  464. $.fn.mustache = function (view, partials) {
  465. return $(this).map(function (i, elm) {
  466. var template = $(elm).html().trim();
  467. var output = $.mustache(template, view, partials);
  468. return $(output).get();
  469. });
  470. };
  471. })(jQuery);