jquery.tmpl.js 24 KB

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