Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

493 рядки
23 KiB

  1. /*
  2. * jQuery Templating Plugin
  3. * Copyright 2010, John Resig
  4. * Dual licensed under the MIT or GPL Version 2 licenses.
  5. */
  6. (function(jQuery, undefined) {
  7. var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
  8. newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
  9. function newTmplItem(options, parentItem, fn, data) {
  10. // Returns a template item data structure for a new rendered instance of a template (a 'template item').
  11. // The content field is a hierarchical array of strings and nested items (to be
  12. // removed and replaced by nodes field of dom elements, once inserted in DOM).
  13. var newItem = {
  14. data: data || (parentItem ? parentItem.data : {}),
  15. _wrap: parentItem ? parentItem._wrap : null,
  16. tmpl: null,
  17. parent: parentItem || null,
  18. nodes: [],
  19. calls: tiCalls,
  20. nest: tiNest,
  21. wrap: tiWrap,
  22. html: tiHtml,
  23. update: tiUpdate
  24. };
  25. if (options) {
  26. jQuery.extend(newItem, options, { nodes: [], parent: parentItem });
  27. }
  28. if (fn) {
  29. // Build the hierarchical content to be used during insertion into DOM
  30. newItem.tmpl = fn;
  31. newItem._ctnt = newItem._ctnt || newItem.tmpl(jQuery, newItem);
  32. newItem.key = ++itemKey;
  33. // Keep track of new template item, until it is stored as jQuery Data on DOM element
  34. (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
  35. }
  36. return newItem;
  37. }
  38. // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
  39. jQuery.each({
  40. appendTo: "append",
  41. prependTo: "prepend",
  42. insertBefore: "before",
  43. insertAfter: "after",
  44. replaceAll: "replaceWith"
  45. }, function(name, original) {
  46. jQuery.fn[ name ] = function(selector) {
  47. var ret = [], insert = jQuery(selector), elems, i, l, tmplItems,
  48. parent = this.length === 1 && this[0].parentNode;
  49. appendToTmplItems = newTmplItems || {};
  50. if (parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1) {
  51. insert[ original ](this[0]);
  52. ret = this;
  53. } else {
  54. for (i = 0,l = insert.length; i < l; i++) {
  55. cloneIndex = i;
  56. elems = (i > 0 ? this.clone(true) : this).get();
  57. jQuery.fn[ original ].apply(jQuery(insert[i]), elems);
  58. ret = ret.concat(elems);
  59. }
  60. cloneIndex = 0;
  61. ret = this.pushStack(ret, name, insert.selector);
  62. }
  63. tmplItems = appendToTmplItems;
  64. appendToTmplItems = null;
  65. jQuery.tmpl.complete(tmplItems);
  66. return ret;
  67. };
  68. });
  69. jQuery.fn.extend({
  70. // Use first wrapped element as template markup.
  71. // Return wrapped set of template items, obtained by rendering template against data.
  72. tmpl: function(data, options, parentItem) {
  73. return jQuery.tmpl(this[0], data, options, parentItem);
  74. },
  75. // Find which rendered template item the first wrapped DOM element belongs to
  76. tmplItem: function() {
  77. return jQuery.tmplItem(this[0]);
  78. },
  79. // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
  80. template: function(name) {
  81. return jQuery.template(name, this[0]);
  82. },
  83. domManip: function(args, table, callback, options) {
  84. // This appears to be a bug in the appendTo, etc. implementation
  85. // it should be doing .call() instead of .apply(). See #6227
  86. if (args[0] && args[0].nodeType) {
  87. var dmArgs = jQuery.makeArray(arguments), argsLength = args.length, i = 0, tmplItem;
  88. while (i < argsLength && !(tmplItem = jQuery.data(args[i++], "tmplItem"))) {
  89. }
  90. if (argsLength > 1) {
  91. dmArgs[0] = [jQuery.makeArray(args)];
  92. }
  93. if (tmplItem && cloneIndex) {
  94. dmArgs[2] = function(fragClone) {
  95. // Handler called by oldManip when rendered template has been inserted into DOM.
  96. jQuery.tmpl.afterManip(this, fragClone, callback);
  97. };
  98. }
  99. oldManip.apply(this, dmArgs);
  100. } else {
  101. oldManip.apply(this, arguments);
  102. }
  103. cloneIndex = 0;
  104. if (!appendToTmplItems) {
  105. jQuery.tmpl.complete(newTmplItems);
  106. }
  107. return this;
  108. }
  109. });
  110. jQuery.extend({
  111. // Return wrapped set of template items, obtained by rendering template against data.
  112. tmpl: function(tmpl, data, options, parentItem) {
  113. var ret, topLevel = !parentItem;
  114. if (topLevel) {
  115. // This is a top-level tmpl call (not from a nested template using {{tmpl}})
  116. parentItem = topTmplItem;
  117. tmpl = jQuery.template[tmpl] || jQuery.template(null, tmpl);
  118. wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
  119. } else if (!tmpl) {
  120. // The template item is already associated with DOM - this is a refresh.
  121. // Re-evaluate rendered template for the parentItem
  122. tmpl = parentItem.tmpl;
  123. newTmplItems[parentItem.key] = parentItem;
  124. parentItem.nodes = [];
  125. if (parentItem.wrapped) {
  126. updateWrapped(parentItem, parentItem.wrapped);
  127. }
  128. // Rebuild, without creating a new template item
  129. return jQuery(build(parentItem, null, parentItem.tmpl(jQuery, parentItem)));
  130. }
  131. if (!tmpl) {
  132. return []; // Could throw...
  133. }
  134. if (typeof data === "function") {
  135. data = data.call(parentItem || {});
  136. }
  137. if (options && options.wrapped) {
  138. updateWrapped(options, options.wrapped);
  139. }
  140. ret = jQuery.isArray(data) ?
  141. jQuery.map(data, function(dataItem) {
  142. return dataItem ? newTmplItem(options, parentItem, tmpl, dataItem) : null;
  143. }) :
  144. [ newTmplItem(options, parentItem, tmpl, data) ];
  145. return topLevel ? jQuery(build(parentItem, null, ret)) : ret;
  146. },
  147. // Return rendered template item for an element.
  148. tmplItem: function(elem) {
  149. var tmplItem;
  150. if (elem instanceof jQuery) {
  151. elem = elem[0];
  152. }
  153. while (elem && elem.nodeType === 1 && !(tmplItem = jQuery.data(elem, "tmplItem")) && (elem = elem.parentNode)) {
  154. }
  155. return tmplItem || topTmplItem;
  156. },
  157. // Set:
  158. // Use $.template( name, tmpl ) to cache a named template,
  159. // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
  160. // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
  161. // Get:
  162. // Use $.template( name ) to access a cached template.
  163. // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
  164. // will return the compiled template, without adding a name reference.
  165. // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
  166. // to $.template( null, templateString )
  167. template: function(name, tmpl) {
  168. if (tmpl) {
  169. // Compile template and associate with name
  170. if (typeof tmpl === "string") {
  171. // This is an HTML string being passed directly in.
  172. tmpl = buildTmplFn(tmpl)
  173. } else if (tmpl instanceof jQuery) {
  174. tmpl = tmpl[0] || {};
  175. }
  176. if (tmpl.nodeType) {
  177. // If this is a template block, use cached copy, or generate tmpl function and cache.
  178. tmpl = jQuery.data(tmpl, "tmpl") || jQuery.data(tmpl, "tmpl", buildTmplFn(tmpl.innerHTML));
  179. }
  180. return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
  181. }
  182. // Return named compiled template
  183. return name ? (typeof name !== "string" ? jQuery.template(null, name) :
  184. (jQuery.template[name] ||
  185. // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
  186. jQuery.template(null, htmlExpr.test(name) ? name : jQuery(name)))) : null;
  187. },
  188. encode: function(text) {
  189. // Do HTML encoding replacing < > & and ' and " by corresponding entities.
  190. return ("" + text).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
  191. }
  192. });
  193. jQuery.extend(jQuery.tmpl, {
  194. tag: {
  195. "tmpl": {
  196. _default: { $2: "null" },
  197. open: "if($notnull_1){_=_.concat($item.nest($1,$2));}"
  198. // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
  199. // This means that {{tmpl foo}} treats foo as a template (which IS a function).
  200. // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
  201. },
  202. "wrap": {
  203. _default: { $2: "null" },
  204. open: "$item.calls(_,$1,$2);_=[];",
  205. close: "call=$item.calls();_=call._.concat($item.wrap(call,_));"
  206. },
  207. "each": {
  208. _default: { $2: "$index, $value" },
  209. open: "if($notnull_1){$.each($1a,function($2){with(this){",
  210. close: "}});}"
  211. },
  212. "if": {
  213. open: "if(($notnull_1) && $1a){",
  214. close: "}"
  215. },
  216. "else": {
  217. _default: { $1: "true" },
  218. open: "}else if(($notnull_1) && $1a){"
  219. },
  220. "html": {
  221. // Unecoded expression evaluation.
  222. open: "if($notnull_1){_.push($1a);}"
  223. },
  224. "=": {
  225. // Encoded expression evaluation. Abbreviated form is ${}.
  226. _default: { $1: "$data" },
  227. open: "if($notnull_1){_.push($.encode($1a));}"
  228. },
  229. "!": {
  230. // Comment tag. Skipped by parser
  231. open: ""
  232. }
  233. },
  234. // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
  235. complete: function(items) {
  236. newTmplItems = {};
  237. },
  238. // Call this from code which overrides domManip, or equivalent
  239. // Manage cloning/storing template items etc.
  240. afterManip: function afterManip(elem, fragClone, callback) {
  241. // Provides cloned fragment ready for fixup prior to and after insertion into DOM
  242. var content = fragClone.nodeType === 11 ?
  243. jQuery.makeArray(fragClone.childNodes) :
  244. fragClone.nodeType === 1 ? [fragClone] : [];
  245. // Return fragment to original caller (e.g. append) for DOM insertion
  246. callback.call(elem, fragClone);
  247. // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
  248. storeTmplItems(content);
  249. cloneIndex++;
  250. }
  251. });
  252. //========================== Private helper functions, used by code above ==========================
  253. function build(tmplItem, nested, content) {
  254. // Convert hierarchical content into flat string array
  255. // and finally return array of fragments ready for DOM insertion
  256. var frag, ret = content ? jQuery.map(content, function(item) {
  257. return (typeof item === "string") ?
  258. // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
  259. (tmplItem.key ? item.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2") : item) :
  260. // This is a child template item. Build nested template.
  261. build(item, tmplItem, item._ctnt);
  262. }) :
  263. // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
  264. tmplItem;
  265. if (nested) {
  266. return ret;
  267. }
  268. // top-level template
  269. ret = ret.join("");
  270. // Support templates which have initial or final text nodes, or consist only of text
  271. // Also support HTML entities within the HTML markup.
  272. ret.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function(all, before, middle, after) {
  273. frag = jQuery(middle).get();
  274. storeTmplItems(frag);
  275. if (before) {
  276. frag = unencode(before).concat(frag);
  277. }
  278. if (after) {
  279. frag = frag.concat(unencode(after));
  280. }
  281. });
  282. return frag ? frag : unencode(ret);
  283. }
  284. function unencode(text) {
  285. // Use createElement, since createTextNode will not render HTML entities correctly
  286. var el = document.createElement("div");
  287. el.innerHTML = text;
  288. return jQuery.makeArray(el.childNodes);
  289. }
  290. // Generate a reusable function that will serve to render a template against data
  291. function buildTmplFn(markup) {
  292. return new Function("jQuery", "$item",
  293. "var $=jQuery,call,_=[],$data=$item.data;" +
  294. // Introduce the data as local variables using with(){}
  295. "with($data){_.push('" +
  296. // Convert the template into pure JavaScript
  297. jQuery.trim(markup)
  298. .replace(/([\\'])/g, "\\$1")
  299. .replace(/[\r\t\n]/g, " ")
  300. .replace(/\$\{([^\}]*)\}/g, "{{= $1}}")
  301. .replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
  302. function(all, slash, type, fnargs, target, parens, args) {
  303. var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
  304. if (!tag) {
  305. throw "Template command not found: " + type;
  306. }
  307. def = tag._default || [];
  308. if (parens && !/\w$/.test(target)) {
  309. target += parens;
  310. parens = "";
  311. }
  312. if (target) {
  313. target = unescape(target);
  314. args = args ? ("," + unescape(args) + ")") : (parens ? ")" : "");
  315. // Support for target being things like a.toLowerCase();
  316. // In that case don't call with template item as 'this' pointer. Just evaluate...
  317. expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($item" + args)) : target;
  318. exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
  319. } else {
  320. exprAutoFnDetect = expr = def.$1 || "null";
  321. }
  322. fnargs = unescape(fnargs);
  323. return "');" +
  324. tag[ slash ? "close" : "open" ]
  325. .split("$notnull_1").join(target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true")
  326. .split("$1a").join(exprAutoFnDetect)
  327. .split("$1").join(expr)
  328. .split("$2").join(fnargs ?
  329. fnargs.replace(/\s*([^\(]+)\s*(\((.*?)\))?/g, function(all, name, parens, params) {
  330. params = params ? ("," + params + ")") : (parens ? ")" : "");
  331. return params ? ("(" + name + ").call($item" + params) : all;
  332. })
  333. : (def.$2 || "")
  334. ) +
  335. "_.push('";
  336. }) +
  337. "');}return _;"
  338. );
  339. }
  340. function updateWrapped(options, wrapped) {
  341. // Build the wrapped content.
  342. options._wrap = build(options, true,
  343. // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
  344. jQuery.isArray(wrapped) ? wrapped : [htmlExpr.test(wrapped) ? wrapped : jQuery(wrapped).html()]
  345. ).join("");
  346. }
  347. function unescape(args) {
  348. return args ? args.replace(/\\'/g, "'").replace(/\\\\/g, "\\") : null;
  349. }
  350. function outerHtml(elem) {
  351. var div = document.createElement("div");
  352. div.appendChild(elem.cloneNode(true));
  353. return div.innerHTML;
  354. }
  355. // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
  356. function storeTmplItems(content) {
  357. var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
  358. for (i = 0,l = content.length; i < l; i++) {
  359. if ((elem = content[i]).nodeType !== 1) {
  360. continue;
  361. }
  362. elems = elem.getElementsByTagName("*");
  363. for (m = elems.length - 1; m >= 0; m--) {
  364. processItemKey(elems[m]);
  365. }
  366. processItemKey(elem);
  367. }
  368. function processItemKey(el) {
  369. var pntKey, pntNode = el, pntItem, tmplItem, key;
  370. // Ensure that each rendered template inserted into the DOM has its own template item,
  371. if ((key = el.getAttribute(tmplItmAtt))) {
  372. while (pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute(tmplItmAtt))) {
  373. }
  374. if (pntKey !== key) {
  375. // The next ancestor with a _tmplitem expando is on a different key than this one.
  376. // So this is a top-level element within this template item
  377. // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
  378. pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute(tmplItmAtt) || 0)) : 0;
  379. if (!(tmplItem = newTmplItems[key])) {
  380. // The item is for wrapped content, and was copied from the temporary parent wrappedItem.
  381. tmplItem = wrappedItems[key];
  382. tmplItem = newTmplItem(tmplItem, newTmplItems[pntNode] || wrappedItems[pntNode], null, true);
  383. tmplItem.key = ++itemKey;
  384. newTmplItems[itemKey] = tmplItem;
  385. }
  386. if (cloneIndex) {
  387. cloneTmplItem(key);
  388. }
  389. }
  390. el.removeAttribute(tmplItmAtt);
  391. } else if (cloneIndex && (tmplItem = jQuery.data(el, "tmplItem"))) {
  392. // This was a rendered element, cloned during append or appendTo etc.
  393. // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
  394. cloneTmplItem(tmplItem.key);
  395. newTmplItems[tmplItem.key] = tmplItem;
  396. pntNode = jQuery.data(el.parentNode, "tmplItem");
  397. pntNode = pntNode ? pntNode.key : 0;
  398. }
  399. if (tmplItem) {
  400. pntItem = tmplItem;
  401. // Find the template item of the parent element.
  402. // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
  403. while (pntItem && pntItem.key != pntNode) {
  404. // Add this element as a top-level node for this rendered template item, as well as for any
  405. // ancestor items between this item and the item of its parent element
  406. pntItem.nodes.push(el);
  407. pntItem = pntItem.parent;
  408. }
  409. // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
  410. delete tmplItem._ctnt;
  411. delete tmplItem._wrap;
  412. // Store template item as jQuery data on the element
  413. jQuery.data(el, "tmplItem", tmplItem);
  414. }
  415. function cloneTmplItem(key) {
  416. key = key + keySuffix;
  417. tmplItem = newClonedItems[key] =
  418. (newClonedItems[key] || newTmplItem(tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true));
  419. }
  420. }
  421. }
  422. //---- Helper functions for template item ----
  423. function tiCalls(content, tmpl, data, options) {
  424. if (!content) {
  425. return stack.pop();
  426. }
  427. stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });
  428. }
  429. function tiNest(tmpl, data, options) {
  430. // nested template, using {{tmpl}} tag
  431. return jQuery.tmpl(jQuery.template(tmpl), data, options, this);
  432. }
  433. function tiWrap(call, wrapped) {
  434. // nested template, using {{wrap}} tag
  435. var options = call.options || {};
  436. options.wrapped = wrapped;
  437. // Apply the template, which may incorporate wrapped content,
  438. return jQuery.tmpl(jQuery.template(call.tmpl), call.data, options, call.item);
  439. }
  440. function tiHtml(filter, textOnly) {
  441. var wrapped = this._wrap;
  442. return jQuery.map(
  443. jQuery(jQuery.isArray(wrapped) ? wrapped.join("") : wrapped).filter(filter || "*"),
  444. function(e) {
  445. return textOnly ?
  446. e.innerText || e.textContent :
  447. e.outerHTML || outerHtml(e);
  448. });
  449. }
  450. function tiUpdate() {
  451. var coll = this.nodes;
  452. jQuery.tmpl(null, null, null, this).insertBefore(coll[0]);
  453. jQuery(coll).remove();
  454. }
  455. })(jQuery);