dom.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  1. define("dojox/dtl/dom", [
  2. "dojo/_base/lang",
  3. "./_base",
  4. "dojox/string/tokenize",
  5. "./Context",
  6. "dojo/dom",
  7. "dojo/dom-construct",
  8. "dojo/_base/html",
  9. "dojo/_base/array",
  10. "dojo/_base/connect",
  11. "dojo/_base/sniff"
  12. ], function(lang,dd,Tokenize,context,dom,domconstruct,html,array,connect,has){
  13. /*=====
  14. Tokenize = dojox.string.tokenize;
  15. dd = dojox.dtl;
  16. =====*/
  17. dd.BOOLS = {checked: 1, disabled: 1, readonly: 1};
  18. dd.TOKEN_CHANGE = -11;
  19. dd.TOKEN_ATTR = -12;
  20. dd.TOKEN_CUSTOM = -13;
  21. dd.TOKEN_NODE = 1;
  22. var ddt = dd.text;
  23. var ddh = dd.dom = {
  24. _attributes: {},
  25. _uppers: {},
  26. _re4: /^function anonymous\(\)\s*{\s*(.*)\s*}$/,
  27. _reTrim: /(?:^[\n\s]*(\{%)?\s*|\s*(%\})?[\n\s]*$)/g,
  28. _reSplit: /\s*%\}[\n\s]*\{%\s*/g,
  29. getTemplate: function(text){
  30. if(typeof this._commentable == "undefined"){
  31. // Check to see if the browser can handle comments
  32. this._commentable = false;
  33. var div = document.createElement("div"), comment = "Test comment handling, and long comments, using comments whenever possible.";
  34. div.innerHTML = "<!--" + comment + "-->";
  35. if(div.childNodes.length && div.firstChild.nodeType == 8 && div.firstChild.data == comment){
  36. this._commentable = true;
  37. }
  38. }
  39. if(!this._commentable){
  40. // Strip comments
  41. text = text.replace(/<!--({({|%).*?(%|})})-->/g, "$1");
  42. }
  43. if(has("ie")){
  44. text = text.replace(/\b(checked|disabled|readonly|style)="/g, 't$1="');
  45. }
  46. text = text.replace(/\bstyle="/g, 'tstyle="');
  47. var match;
  48. var table = has("webkit");
  49. var pairs = [ // Format: [enable, parent, allowed children (first for nesting), nestings]
  50. [true, "select", "option"],
  51. [table, "tr", "td|th"],
  52. [table, "thead", "tr", "th"],
  53. [table, "tbody", "tr", "td"],
  54. [table, "table", "tbody|thead|tr", "tr", "td"]
  55. ];
  56. var replacements = [];
  57. // Some tags can't contain text. So we wrap the text in tags that they can have.
  58. for(var i = 0, pair; pair = pairs[i]; i++){
  59. if(!pair[0]){
  60. continue;
  61. }
  62. if(text.indexOf("<" + pair[1]) != -1){
  63. var selectRe = new RegExp("<" + pair[1] + "(?:.|\n)*?>((?:.|\n)+?)</" + pair[1] + ">", "ig");
  64. tagLoop: while(match = selectRe.exec(text)){
  65. // Do it like this to make sure we don't double-wrap
  66. var inners = pair[2].split("|");
  67. var innerRe = [];
  68. for(var j = 0, inner; inner = inners[j]; j++){
  69. innerRe.push("<" + inner + "(?:.|\n)*?>(?:.|\n)*?</" + inner + ">");
  70. }
  71. var tags = [];
  72. var tokens = Tokenize(match[1], new RegExp("(" + innerRe.join("|") + ")", "ig"), function(data){
  73. var tag = /<(\w+)/.exec(data)[1];
  74. if(!tags[tag]){
  75. tags[tag] = true;
  76. tags.push(tag);
  77. }
  78. return {data: data};
  79. });
  80. if(tags.length){
  81. var tag = (tags.length == 1) ? tags[0] : pair[2].split("|")[0];
  82. var replace = [];
  83. for(var j = 0, jl = tokens.length; j < jl; j++) {
  84. var token = tokens[j];
  85. if(lang.isObject(token)){
  86. replace.push(token.data);
  87. }else{
  88. var stripped = token.replace(this._reTrim, "");
  89. if(!stripped){ continue; }
  90. token = stripped.split(this._reSplit);
  91. for(var k = 0, kl = token.length; k < kl; k++){
  92. var replacement = "";
  93. for(var p = 2, pl = pair.length; p < pl; p++){
  94. if(p == 2){
  95. replacement += "<" + tag + ' dtlinstruction="{% ' + token[k].replace('"', '\\"') + ' %}">';
  96. }else if(tag == pair[p]) {
  97. continue;
  98. }else{
  99. replacement += "<" + pair[p] + ">";
  100. }
  101. }
  102. replacement += "DTL";
  103. for(var p = pair.length - 1; p > 1; p--){
  104. if(p == 2){
  105. replacement += "</" + tag + ">";
  106. }else if(tag == pair[p]) {
  107. continue;
  108. }else{
  109. replacement += "</" + pair[p] + ">";
  110. }
  111. }
  112. replace.push("\xFF" + replacements.length);
  113. replacements.push(replacement);
  114. }
  115. }
  116. }
  117. text = text.replace(match[1], replace.join(""));
  118. }
  119. }
  120. }
  121. }
  122. for(var i = replacements.length; i--;){
  123. text = text.replace("\xFF" + i, replacements[i]);
  124. }
  125. var re = /\b([a-zA-Z_:][a-zA-Z0-9_\-\.:]*)=['"]/g;
  126. while(match = re.exec(text)){
  127. var lower = match[1].toLowerCase();
  128. if(lower == "dtlinstruction"){ continue; }
  129. if(lower != match[1]){
  130. this._uppers[lower] = match[1];
  131. }
  132. this._attributes[lower] = true;
  133. }
  134. var div = document.createElement("div");
  135. div.innerHTML = text;
  136. var output = {nodes: []};
  137. while(div.childNodes.length){
  138. output.nodes.push(div.removeChild(div.childNodes[0]))
  139. }
  140. return output;
  141. },
  142. tokenize: function(/*Node*/ nodes){
  143. var tokens = [];
  144. for(var i = 0, node; node = nodes[i++];){
  145. if(node.nodeType != 1){
  146. this.__tokenize(node, tokens);
  147. }else{
  148. this._tokenize(node, tokens);
  149. }
  150. }
  151. return tokens;
  152. },
  153. _swallowed: [],
  154. _tokenize: function(/*Node*/ node, /*Array*/ tokens){
  155. var first = false;
  156. var swallowed = this._swallowed;
  157. var i, j, tag, child;
  158. if(!tokens.first){
  159. // Try to efficiently associate tags that use an attribute to
  160. // remove the node from DOM (eg dojoType) so that we can efficiently
  161. // locate them later in the tokenizing.
  162. first = tokens.first = true;
  163. var tags = dd.register.getAttributeTags();
  164. for(i = 0; tag = tags[i]; i++){
  165. try{
  166. (tag[2])({ swallowNode: function(){ throw 1; }}, new dd.Token(dd.TOKEN_ATTR, ""));
  167. }catch(e){
  168. swallowed.push(tag);
  169. }
  170. }
  171. }
  172. for(i = 0; tag = swallowed[i]; i++){
  173. var text = node.getAttribute(tag[0]);
  174. if(text){
  175. var swallowed = false;
  176. var custom = (tag[2])({ swallowNode: function(){ swallowed = true; return node; }}, new dd.Token(dd.TOKEN_ATTR, tag[0] + " " + text));
  177. if(swallowed){
  178. if(node.parentNode && node.parentNode.removeChild){
  179. node.parentNode.removeChild(node);
  180. }
  181. tokens.push([dd.TOKEN_CUSTOM, custom]);
  182. return;
  183. }
  184. }
  185. }
  186. var children = [];
  187. if(has("ie") && node.tagName == "SCRIPT"){
  188. children.push({
  189. nodeType: 3,
  190. data: node.text
  191. });
  192. node.text = "";
  193. }else{
  194. for(i = 0; child = node.childNodes[i]; i++){
  195. children.push(child);
  196. }
  197. }
  198. tokens.push([dd.TOKEN_NODE, node]);
  199. var change = false;
  200. if(children.length){
  201. // Only do a change request if we need to
  202. tokens.push([dd.TOKEN_CHANGE, node]);
  203. change = true;
  204. }
  205. for(var key in this._attributes){
  206. var clear = false;
  207. var value = "";
  208. if(key == "class"){
  209. value = node.className || value;
  210. }else if(key == "for"){
  211. value = node.htmlFor || value;
  212. }else if(key == "value" && node.value == node.innerHTML){
  213. // Sometimes .value is set the same as the contents of the item (button)
  214. continue;
  215. }else if(node.getAttribute){
  216. value = node.getAttribute(key, 2) || value;
  217. if(key == "href" || key == "src"){
  218. if(has("ie")){
  219. var hash = location.href.lastIndexOf(location.hash);
  220. var href = location.href.substring(0, hash).split("/");
  221. href.pop();
  222. href = href.join("/") + "/";
  223. if(value.indexOf(href) == 0){
  224. value = value.replace(href, "");
  225. }
  226. value = decodeURIComponent(value);
  227. }
  228. }else if(key == "tstyle"){
  229. clear = key; // Placeholder because we can't use style
  230. key = "style";
  231. }else if(dd.BOOLS[key.slice(1)] && lang.trim(value)){
  232. key = key.slice(1);
  233. }else if(this._uppers[key] && lang.trim(value)){
  234. clear = this._uppers[key]; // Replaced by lowercase
  235. }
  236. }
  237. if(clear){
  238. // Clear out values that are different than will
  239. // be used in plugins
  240. node.setAttribute(clear, "");
  241. node.removeAttribute(clear);
  242. }
  243. if(typeof value == "function"){
  244. value = value.toString().replace(this._re4, "$1");
  245. }
  246. if(!change){
  247. // Only do a change request if we need to
  248. tokens.push([dd.TOKEN_CHANGE, node]);
  249. change = true;
  250. }
  251. // We'll have to resolve attributes during parsing (some ref plugins)
  252. tokens.push([dd.TOKEN_ATTR, node, key, value]);
  253. }
  254. for(i = 0, child; child = children[i]; i++){
  255. if(child.nodeType == 1){
  256. var instruction = child.getAttribute("dtlinstruction");
  257. if(instruction){
  258. child.parentNode.removeChild(child);
  259. child = {
  260. nodeType: 8,
  261. data: instruction
  262. };
  263. }
  264. }
  265. this.__tokenize(child, tokens);
  266. }
  267. if(!first && node.parentNode && node.parentNode.tagName){
  268. if(change){
  269. tokens.push([dd.TOKEN_CHANGE, node, true]);
  270. }
  271. tokens.push([dd.TOKEN_CHANGE, node.parentNode]);
  272. node.parentNode.removeChild(node);
  273. }else{
  274. // If this node is parentless, it's a base node, so we have to "up" change to itself
  275. // and note that it's a top-level to watch for errors
  276. tokens.push([dd.TOKEN_CHANGE, node, true, true]);
  277. }
  278. },
  279. __tokenize: function(child, tokens){
  280. var data = child.data;
  281. switch(child.nodeType){
  282. case 1:
  283. this._tokenize(child, tokens);
  284. return;
  285. case 3:
  286. if(data.match(/[^\s\n]/) && (data.indexOf("{{") != -1 || data.indexOf("{%") != -1)){
  287. var texts = ddt.tokenize(data);
  288. for(var j = 0, text; text = texts[j]; j++){
  289. if(typeof text == "string"){
  290. tokens.push([dd.TOKEN_TEXT, text]);
  291. }else{
  292. tokens.push(text);
  293. }
  294. }
  295. }else{
  296. tokens.push([child.nodeType, child]);
  297. }
  298. if(child.parentNode) child.parentNode.removeChild(child);
  299. return;
  300. case 8:
  301. if(data.indexOf("{%") == 0){
  302. var text = lang.trim(data.slice(2, -2));
  303. if(text.substr(0, 5) == "load "){
  304. var parts = lang.trim(text).split(/\s+/g);
  305. for(var i = 1, part; part = parts[i]; i++){
  306. if (/\./.test(part)){
  307. part = part.replace(/\./g,"/");
  308. }
  309. require([part]);
  310. }
  311. }
  312. tokens.push([dd.TOKEN_BLOCK, text]);
  313. }
  314. if(data.indexOf("{{") == 0){
  315. tokens.push([dd.TOKEN_VAR, lang.trim(data.slice(2, -2))]);
  316. }
  317. if(child.parentNode) child.parentNode.removeChild(child);
  318. return;
  319. }
  320. }
  321. };
  322. dd.DomTemplate = lang.extend(function(/*String|DOMNode|dojo._Url*/ obj){
  323. // summary: The template class for DOM templating.
  324. if(!obj.nodes){
  325. var node = dom.byId(obj);
  326. if(node && node.nodeType == 1){
  327. array.forEach(["class", "src", "href", "name", "value"], function(item){
  328. ddh._attributes[item] = true;
  329. });
  330. obj = {
  331. nodes: [node]
  332. };
  333. }else{
  334. if(typeof obj == "object"){
  335. obj = ddt.getTemplateString(obj);
  336. }
  337. obj = ddh.getTemplate(obj);
  338. }
  339. }
  340. var tokens = ddh.tokenize(obj.nodes);
  341. if(dd.tests){
  342. this.tokens = tokens.slice(0);
  343. }
  344. var parser = new dd._DomParser(tokens);
  345. this.nodelist = parser.parse();
  346. },
  347. {
  348. _count: 0,
  349. _re: /\bdojo:([a-zA-Z0-9_]+)\b/g,
  350. setClass: function(/*String*/str){
  351. // summary: Sets the specified class name on the root node.
  352. this.getRootNode().className = str;
  353. },
  354. getRootNode: function(){
  355. // summary: Returns the template root node.
  356. return this.buffer.rootNode;
  357. },
  358. getBuffer: function(){
  359. // summary: Returns a new buffer.
  360. return new dd.DomBuffer();
  361. },
  362. render: function(/*dojox.dtl.Context?*/context, /*concatenable?*/buffer){
  363. // summary: Renders this template.
  364. buffer = this.buffer = buffer || this.getBuffer();
  365. this.rootNode = null;
  366. var output = this.nodelist.render(context || new dd.Context({}), buffer);
  367. for(var i = 0, node; node = buffer._cache[i]; i++){
  368. if(node._cache){
  369. node._cache.length = 0;
  370. }
  371. }
  372. return output;
  373. },
  374. unrender: function(context, buffer){
  375. return this.nodelist.unrender(context, buffer);
  376. }
  377. });
  378. dd.DomBuffer = lang.extend(function(/*Node*/ parent){
  379. // summary: Allows the manipulation of DOM
  380. // description:
  381. // Use this to append a child, change the parent, or
  382. // change the attribute of the current node.
  383. this._parent = parent;
  384. this._cache = [];
  385. },
  386. {
  387. concat: function(/*DOMNode*/ node){
  388. var parent = this._parent;
  389. if(parent && node.parentNode && node.parentNode === parent && !parent._dirty){
  390. return this;
  391. }
  392. if(node.nodeType == 1 && !this.rootNode){
  393. this.rootNode = node || true;
  394. return this;
  395. }
  396. if(!parent){
  397. if(node.nodeType == 3 && lang.trim(node.data)){
  398. throw new Error("Text should not exist outside of the root node in template");
  399. }
  400. return this;
  401. }
  402. if(this._closed){
  403. if(node.nodeType == 3 && !lang.trim(node.data)){
  404. return this;
  405. }else{
  406. throw new Error("Content should not exist outside of the root node in template");
  407. }
  408. }
  409. if(parent._dirty){
  410. if(node._drawn && node.parentNode == parent){
  411. var caches = parent._cache;
  412. if(caches){
  413. for(var i = 0, cache; cache = caches[i]; i++){
  414. this.onAddNode && this.onAddNode(cache);
  415. parent.insertBefore(cache, node);
  416. this.onAddNodeComplete && this.onAddNodeComplete(cache);
  417. }
  418. caches.length = 0;
  419. }
  420. }
  421. parent._dirty = false;
  422. }
  423. if(!parent._cache){
  424. parent._cache = [];
  425. this._cache.push(parent);
  426. }
  427. parent._dirty = true;
  428. parent._cache.push(node);
  429. return this;
  430. },
  431. remove: function(/*String|DomNode*/obj){
  432. if(typeof obj == "string"){
  433. if(this._parent){
  434. this._parent.removeAttribute(obj);
  435. }
  436. }else{
  437. if(obj.nodeType == 1 && !this.getRootNode() && !this._removed){
  438. this._removed = true;
  439. return this;
  440. }
  441. if(obj.parentNode){
  442. this.onRemoveNode && this.onRemoveNode(obj);
  443. if(obj.parentNode){
  444. obj.parentNode.removeChild(obj);
  445. }
  446. }
  447. }
  448. return this;
  449. },
  450. setAttribute: function(key, value){
  451. var old = html.attr(this._parent, key);
  452. if(this.onChangeAttribute && old != value){
  453. this.onChangeAttribute(this._parent, key, old, value);
  454. }
  455. if(key == "style"){
  456. //console.log(value);
  457. this._parent.style.cssText = value;
  458. }else{
  459. html.attr(this._parent, key, value);
  460. //console.log(this._parent, key, value);
  461. if(key == "value"){
  462. this._parent.setAttribute(key, value);
  463. }
  464. }
  465. return this;
  466. },
  467. addEvent: function(context, type, fn, /*Array|Function*/ args){
  468. if(!context.getThis()){ throw new Error("You must use Context.setObject(instance)"); }
  469. this.onAddEvent && this.onAddEvent(this.getParent(), type, fn);
  470. var resolved = fn;
  471. if(lang.isArray(args)){
  472. resolved = function(e){
  473. this[fn].apply(this, [e].concat(args));
  474. }
  475. }
  476. return connect.connect(this.getParent(), type, context.getThis(), resolved);
  477. },
  478. setParent: function(node, /*Boolean?*/ up, /*Boolean?*/ root){
  479. if(!this._parent) this._parent = this._first = node;
  480. if(up && root && node === this._first){
  481. this._closed = true;
  482. }
  483. if(up){
  484. var parent = this._parent;
  485. var script = "";
  486. var ie = has("ie") && parent.tagName == "SCRIPT";
  487. if(ie){
  488. parent.text = "";
  489. }
  490. if(parent._dirty){
  491. var caches = parent._cache;
  492. var select = (parent.tagName == "SELECT" && !parent.options.length);
  493. for(var i = 0, cache; cache = caches[i]; i++){
  494. if(cache !== parent){
  495. this.onAddNode && this.onAddNode(cache);
  496. if(ie){
  497. script += cache.data;
  498. }else{
  499. parent.appendChild(cache);
  500. if(select && cache.defaultSelected && i){
  501. select = i;
  502. }
  503. }
  504. this.onAddNodeComplete && this.onAddNodeComplete(cache);
  505. }
  506. }
  507. if(select){
  508. parent.options.selectedIndex = (typeof select == "number") ? select : 0;
  509. }
  510. caches.length = 0;
  511. parent._dirty = false;
  512. }
  513. if(ie){
  514. parent.text = script;
  515. }
  516. }
  517. this._parent = node;
  518. this.onSetParent && this.onSetParent(node, up, root);
  519. return this;
  520. },
  521. getParent: function(){
  522. return this._parent;
  523. },
  524. getRootNode: function(){
  525. return this.rootNode;
  526. }
  527. /*=====
  528. ,
  529. onSetParent: function(node, up){
  530. // summary: Stub called when setParent is used.
  531. },
  532. onAddNode: function(node){
  533. // summary: Stub called before new nodes are added
  534. },
  535. onAddNodeComplete: function(node){
  536. // summary: Stub called after new nodes are added
  537. },
  538. onRemoveNode: function(node){
  539. // summary: Stub called when nodes are removed
  540. },
  541. onChangeAttribute: function(node, attribute, old, updated){
  542. // summary: Stub called when an attribute is changed
  543. },
  544. onChangeData: function(node, old, updated){
  545. // summary: Stub called when a data in a node is changed
  546. },
  547. onClone: function(from, to){
  548. // summary: Stub called when a node is duplicated
  549. // from: DOMNode
  550. // to: DOMNode
  551. },
  552. onAddEvent: function(node, type, description){
  553. // summary: Stub to call when you're adding an event
  554. // node: DOMNode
  555. // type: String
  556. // description: String
  557. }
  558. =====*/
  559. });
  560. dd._DomNode = lang.extend(function(node){
  561. // summary: Places a node into DOM
  562. this.contents = node;
  563. },
  564. {
  565. render: function(context, buffer){
  566. this._rendered = true;
  567. return buffer.concat(this.contents);
  568. },
  569. unrender: function(context, buffer){
  570. if(!this._rendered){
  571. return buffer;
  572. }
  573. this._rendered = false;
  574. return buffer.remove(this.contents);
  575. },
  576. clone: function(buffer){
  577. return new this.constructor(this.contents);
  578. }
  579. });
  580. dd._DomNodeList = lang.extend(function(/*Node[]*/ nodes){
  581. // summary: A list of any DOM-specific node objects
  582. // description:
  583. // Any object that's used in the constructor or added
  584. // through the push function much implement the
  585. // render, unrender, and clone functions.
  586. this.contents = nodes || [];
  587. },
  588. {
  589. push: function(node){
  590. this.contents.push(node);
  591. },
  592. unshift: function(node){
  593. this.contents.unshift(node);
  594. },
  595. render: function(context, buffer, /*Node*/ instance){
  596. buffer = buffer || dd.DomTemplate.prototype.getBuffer();
  597. if(instance){
  598. var parent = buffer.getParent();
  599. }
  600. for(var i = 0; i < this.contents.length; i++){
  601. buffer = this.contents[i].render(context, buffer);
  602. if(!buffer) throw new Error("Template node render functions must return their buffer");
  603. }
  604. if(parent){
  605. buffer.setParent(parent);
  606. }
  607. return buffer;
  608. },
  609. dummyRender: function(context, buffer, asNode){
  610. // summary: A really expensive way of checking to see how a rendering will look.
  611. // Used in the ifchanged tag
  612. var div = document.createElement("div");
  613. var parent = buffer.getParent();
  614. var old = parent._clone;
  615. // Tell the clone system to attach itself to our new div
  616. parent._clone = div;
  617. var nodelist = this.clone(buffer, div);
  618. if(old){
  619. // Restore state if there was a previous clone
  620. parent._clone = old;
  621. }else{
  622. // Remove if there was no clone
  623. parent._clone = null;
  624. }
  625. buffer = dd.DomTemplate.prototype.getBuffer();
  626. nodelist.unshift(new dd.ChangeNode(div));
  627. nodelist.unshift(new dd._DomNode(div));
  628. nodelist.push(new dd.ChangeNode(div, true));
  629. nodelist.render(context, buffer);
  630. if(asNode){
  631. return buffer.getRootNode();
  632. }
  633. var html = div.innerHTML;
  634. return (has("ie")) ? domconstruct.replace(/\s*_(dirty|clone)="[^"]*"/g, "") : html;
  635. },
  636. unrender: function(context, buffer, instance){
  637. if(instance){
  638. var parent = buffer.getParent();
  639. }
  640. for(var i = 0; i < this.contents.length; i++){
  641. buffer = this.contents[i].unrender(context, buffer);
  642. if(!buffer) throw new Error("Template node render functions must return their buffer");
  643. }
  644. if(parent){
  645. buffer.setParent(parent);
  646. }
  647. return buffer;
  648. },
  649. clone: function(buffer){
  650. // summary:
  651. // Used to create an identical copy of a NodeList, useful for things like the for tag.
  652. var parent = buffer.getParent();
  653. var contents = this.contents;
  654. var nodelist = new dd._DomNodeList();
  655. var cloned = [];
  656. for(var i = 0; i < contents.length; i++){
  657. var clone = contents[i].clone(buffer);
  658. if(clone instanceof dd.ChangeNode || clone instanceof dd._DomNode){
  659. var item = clone.contents._clone;
  660. if(item){
  661. clone.contents = item;
  662. }else if(parent != clone.contents && clone instanceof dd._DomNode){
  663. var node = clone.contents;
  664. clone.contents = clone.contents.cloneNode(false);
  665. buffer.onClone && buffer.onClone(node, clone.contents);
  666. cloned.push(node);
  667. node._clone = clone.contents;
  668. }
  669. }
  670. nodelist.push(clone);
  671. }
  672. for(var i = 0, clone; clone = cloned[i]; i++){
  673. clone._clone = null;
  674. }
  675. return nodelist;
  676. },
  677. rtrim: function(){
  678. while(1){
  679. var i = this.contents.length - 1;
  680. if(this.contents[i] instanceof dd._DomTextNode && this.contents[i].isEmpty()){
  681. this.contents.pop();
  682. }else{
  683. break;
  684. }
  685. }
  686. return this;
  687. }
  688. });
  689. dd._DomVarNode = lang.extend(function(str){
  690. // summary: A node to be processed as a variable
  691. // description:
  692. // Will render an object that supports the render function
  693. // and the getRootNode function
  694. this.contents = new dd._Filter(str);
  695. },
  696. {
  697. render: function(context, buffer){
  698. var str = this.contents.resolve(context);
  699. // What type of rendering?
  700. var type = "text";
  701. if(str){
  702. if(str.render && str.getRootNode){
  703. type = "injection";
  704. }else if(str.safe){
  705. if(str.nodeType){
  706. type = "node";
  707. }else if(str.toString){
  708. str = str.toString();
  709. type = "html";
  710. }
  711. }
  712. }
  713. // Has the typed changed?
  714. if(this._type && type != this._type){
  715. this.unrender(context, buffer);
  716. }
  717. this._type = type;
  718. // Now render
  719. switch(type){
  720. case "text":
  721. this._rendered = true;
  722. this._txt = this._txt || document.createTextNode(str);
  723. if(this._txt.data != str){
  724. var old = this._txt.data;
  725. this._txt.data = str;
  726. buffer.onChangeData && buffer.onChangeData(this._txt, old, this._txt.data);
  727. }
  728. return buffer.concat(this._txt);
  729. case "injection":
  730. var root = str.getRootNode();
  731. if(this._rendered && root != this._root){
  732. buffer = this.unrender(context, buffer);
  733. }
  734. this._root = root;
  735. var injected = this._injected = new dd._DomNodeList();
  736. injected.push(new dd.ChangeNode(buffer.getParent()));
  737. injected.push(new dd._DomNode(root));
  738. injected.push(str);
  739. injected.push(new dd.ChangeNode(buffer.getParent()));
  740. this._rendered = true;
  741. return injected.render(context, buffer);
  742. case "node":
  743. this._rendered = true;
  744. if(this._node && this._node != str && this._node.parentNode && this._node.parentNode === buffer.getParent()){
  745. this._node.parentNode.removeChild(this._node);
  746. }
  747. this._node = str;
  748. return buffer.concat(str);
  749. case "html":
  750. if(this._rendered && this._src != str){
  751. buffer = this.unrender(context, buffer);
  752. }
  753. this._src = str;
  754. // This can get reset in the above tag
  755. if(!this._rendered){
  756. this._rendered = true;
  757. this._html = this._html || [];
  758. var div = (this._div = this._div || document.createElement("div"));
  759. div.innerHTML = str;
  760. var children = div.childNodes;
  761. while(children.length){
  762. var removed = div.removeChild(children[0]);
  763. this._html.push(removed);
  764. buffer = buffer.concat(removed);
  765. }
  766. }
  767. return buffer;
  768. default:
  769. return buffer;
  770. }
  771. },
  772. unrender: function(context, buffer){
  773. if(!this._rendered){
  774. return buffer;
  775. }
  776. this._rendered = false;
  777. // Unrender injected nodes
  778. switch(this._type){
  779. case "text":
  780. return buffer.remove(this._txt);
  781. case "injection":
  782. return this._injection.unrender(context, buffer);
  783. case "node":
  784. if(this._node.parentNode === buffer.getParent()){
  785. return buffer.remove(this._node);
  786. }
  787. return buffer;
  788. case "html":
  789. for(var i = 0, l = this._html.length; i < l; i++){
  790. buffer = buffer.remove(this._html[i]);
  791. }
  792. return buffer;
  793. default:
  794. return buffer;
  795. }
  796. },
  797. clone: function(){
  798. return new this.constructor(this.contents.getExpression());
  799. }
  800. });
  801. dd.ChangeNode = lang.extend(function(node, /*Boolean?*/ up, /*Bookean*/ root){
  802. // summary: Changes the parent during render/unrender
  803. this.contents = node;
  804. this.up = up;
  805. this.root = root;
  806. },
  807. {
  808. render: function(context, buffer){
  809. return buffer.setParent(this.contents, this.up, this.root);
  810. },
  811. unrender: function(context, buffer){
  812. if(!buffer.getParent()){
  813. return buffer;
  814. }
  815. return buffer.setParent(this.contents);
  816. },
  817. clone: function(){
  818. return new this.constructor(this.contents, this.up, this.root);
  819. }
  820. });
  821. dd.AttributeNode = lang.extend(function(key, value){
  822. // summary: Works on attributes
  823. this.key = key;
  824. this.value = value;
  825. this.contents = value;
  826. if(this._pool[value]){
  827. this.nodelist = this._pool[value];
  828. }else{
  829. if(!(this.nodelist = dd.quickFilter(value))){
  830. this.nodelist = (new dd.Template(value, true)).nodelist;
  831. }
  832. this._pool[value] = this.nodelist;
  833. }
  834. this.contents = "";
  835. },
  836. {
  837. _pool: {},
  838. render: function(context, buffer){
  839. var key = this.key;
  840. var value = this.nodelist.dummyRender(context);
  841. if(dd.BOOLS[key]){
  842. value = !(value == "false" || value == "undefined" || !value);
  843. }
  844. if(value !== this.contents){
  845. this.contents = value;
  846. return buffer.setAttribute(key, value);
  847. }
  848. return buffer;
  849. },
  850. unrender: function(context, buffer){
  851. this.contents = "";
  852. return buffer.remove(this.key);
  853. },
  854. clone: function(buffer){
  855. return new this.constructor(this.key, this.value);
  856. }
  857. });
  858. dd._DomTextNode = lang.extend(function(str){
  859. // summary: Adds a straight text node without any processing
  860. this.contents = document.createTextNode(str);
  861. this.upcoming = str;
  862. },
  863. {
  864. set: function(data){
  865. this.upcoming = data;
  866. return this;
  867. },
  868. render: function(context, buffer){
  869. if(this.contents.data != this.upcoming){
  870. var old = this.contents.data;
  871. this.contents.data = this.upcoming;
  872. buffer.onChangeData && buffer.onChangeData(this.contents, old, this.upcoming);
  873. }
  874. return buffer.concat(this.contents);
  875. },
  876. unrender: function(context, buffer){
  877. return buffer.remove(this.contents);
  878. },
  879. isEmpty: function(){
  880. return !lang.trim(this.contents.data);
  881. },
  882. clone: function(){
  883. return new this.constructor(this.contents.data);
  884. }
  885. });
  886. dd._DomParser = lang.extend(function(tokens){
  887. // summary: Turn a simple array into a set of objects
  888. // description:
  889. // This is also used by all tags to move through
  890. // the list of nodes.
  891. this.contents = tokens;
  892. },
  893. {
  894. i: 0,
  895. parse: function(/*Array?*/ stop_at){
  896. var terminators = {};
  897. var tokens = this.contents;
  898. if(!stop_at){
  899. stop_at = [];
  900. }
  901. for(var i = 0; i < stop_at.length; i++){
  902. terminators[stop_at[i]] = true;
  903. }
  904. var nodelist = new dd._DomNodeList();
  905. while(this.i < tokens.length){
  906. var token = tokens[this.i++];
  907. var type = token[0];
  908. var value = token[1];
  909. if(type == dd.TOKEN_CUSTOM){
  910. nodelist.push(value);
  911. }else if(type == dd.TOKEN_CHANGE){
  912. var changeNode = new dd.ChangeNode(value, token[2], token[3]);
  913. value[changeNode.attr] = changeNode;
  914. nodelist.push(changeNode);
  915. }else if(type == dd.TOKEN_ATTR){
  916. var fn = ddt.getTag("attr:" + token[2], true);
  917. if(fn && token[3]){
  918. if (token[3].indexOf("{%") != -1 || token[3].indexOf("{{") != -1) {
  919. value.setAttribute(token[2], "");
  920. }
  921. nodelist.push(fn(null, new dd.Token(type, token[2] + " " + token[3])));
  922. }else if(lang.isString(token[3])){
  923. if(token[2] == "style" || token[3].indexOf("{%") != -1 || token[3].indexOf("{{") != -1){
  924. nodelist.push(new dd.AttributeNode(token[2], token[3]));
  925. }else if(lang.trim(token[3])){
  926. try{
  927. html.attr(value, token[2], token[3]);
  928. }catch(e){}
  929. }
  930. }
  931. }else if(type == dd.TOKEN_NODE){
  932. var fn = ddt.getTag("node:" + value.tagName.toLowerCase(), true);
  933. if(fn){
  934. // TODO: We need to move this to tokenization so that it's before the
  935. // node and the parser can be passed here instead of null
  936. nodelist.push(fn(null, new dd.Token(type, value), value.tagName.toLowerCase()));
  937. }
  938. nodelist.push(new dd._DomNode(value));
  939. }else if(type == dd.TOKEN_VAR){
  940. nodelist.push(new dd._DomVarNode(value));
  941. }else if(type == dd.TOKEN_TEXT){
  942. nodelist.push(new dd._DomTextNode(value.data || value));
  943. }else if(type == dd.TOKEN_BLOCK){
  944. if(terminators[value]){
  945. --this.i;
  946. return nodelist;
  947. }
  948. var cmd = value.split(/\s+/g);
  949. if(cmd.length){
  950. cmd = cmd[0];
  951. var fn = ddt.getTag(cmd);
  952. if(typeof fn != "function"){
  953. throw new Error("Function not found for " + cmd);
  954. }
  955. var tpl = fn(this, new dd.Token(type, value));
  956. if(tpl){
  957. nodelist.push(tpl);
  958. }
  959. }
  960. }
  961. }
  962. if(stop_at.length){
  963. throw new Error("Could not find closing tag(s): " + stop_at.toString());
  964. }
  965. return nodelist;
  966. },
  967. next_token: function(){
  968. // summary: Returns the next token in the list.
  969. var token = this.contents[this.i++];
  970. return new dd.Token(token[0], token[1]);
  971. },
  972. delete_first_token: function(){
  973. this.i++;
  974. },
  975. skip_past: function(endtag){
  976. return dd._Parser.prototype.skip_past.call(this, endtag);
  977. },
  978. create_variable_node: function(expr){
  979. return new dd._DomVarNode(expr);
  980. },
  981. create_text_node: function(expr){
  982. return new dd._DomTextNode(expr || "");
  983. },
  984. getTemplate: function(/*String*/ loc){
  985. return new dd.DomTemplate(ddh.getTemplate(loc));
  986. }
  987. });
  988. return dojox.dtl.dom;
  989. });