_TreeView.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.grid._TreeView"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid._TreeView"] = true;
  8. dojo.provide("dojox.grid._TreeView");
  9. dojo.require("dijit._Widget");
  10. dojo.require("dijit._Templated");
  11. dojo.require("dojox.grid._View");
  12. dojo.declare("dojox.grid._Expando", [ dijit._Widget, dijit._Templated ], {
  13. open: false,
  14. toggleClass: "",
  15. itemId: "",
  16. cellIdx: -1,
  17. view: null,
  18. rowNode: null,
  19. rowIdx: -1,
  20. expandoCell: null,
  21. level: 0,
  22. templateString:"<div class=\"dojoxGridExpando\"\n\t><div class=\"dojoxGridExpandoNode\" dojoAttachEvent=\"onclick:onToggle\"\n\t\t><div class=\"dojoxGridExpandoNodeInner\" dojoAttachPoint=\"expandoInner\"></div\n\t></div\n></div>\n",
  23. _toggleRows: function(toggleClass, open){
  24. if(!toggleClass || !this.rowNode){ return; }
  25. if(dojo.query("table.dojoxGridRowTableNeedsRowUpdate").length){
  26. if(this._initialized){
  27. this.view.grid.updateRow(this.rowIdx);
  28. }
  29. return;
  30. }
  31. var self = this;
  32. var g = this.view.grid;
  33. if(g.treeModel){
  34. var p = this._tableRow ? dojo.attr(this._tableRow, "dojoxTreeGridPath") : "";
  35. if(p){
  36. dojo.query("tr[dojoxTreeGridPath^=\"" + p + "/\"]", this.rowNode).forEach(function(n){
  37. var en = dojo.query(".dojoxGridExpando", n)[0];
  38. if(en && en.parentNode && en.parentNode.parentNode &&
  39. !dojo.hasClass(en.parentNode.parentNode, "dojoxGridNoChildren")){
  40. var ew = dijit.byNode(en);
  41. if(ew){
  42. ew._toggleRows(toggleClass, ew.open&&open);
  43. }
  44. }
  45. n.style.display = open ? "" : "none";
  46. });
  47. }
  48. }else{
  49. dojo.query("tr." + toggleClass, this.rowNode).forEach(function(n){
  50. if(dojo.hasClass(n, "dojoxGridExpandoRow")){
  51. var en = dojo.query(".dojoxGridExpando", n)[0];
  52. if(en){
  53. var ew = dijit.byNode(en);
  54. var toggleClass = ew ? ew.toggleClass : en.getAttribute("toggleClass");
  55. var wOpen = ew ? ew.open : self.expandoCell.getOpenState(en.getAttribute("itemId"));
  56. self._toggleRows(toggleClass, wOpen&&open);
  57. }
  58. }
  59. n.style.display = open ? "" : "none";
  60. });
  61. }
  62. },
  63. setOpen: function(open){
  64. if(open && dojo.hasClass(this.domNode, "dojoxGridExpandoLoading")){
  65. open = false;
  66. }
  67. var view = this.view;
  68. var grid = view.grid;
  69. var store = grid.store;
  70. var treeModel = grid.treeModel;
  71. var d = this;
  72. var idx = this.rowIdx;
  73. var me = grid._by_idx[idx];
  74. if(!me){ return; }
  75. if(treeModel && !this._loadedChildren){
  76. if(open){
  77. // Do this to make sure our children are fully-loaded
  78. var itm = grid.getItem(dojo.attr(this._tableRow, "dojoxTreeGridPath"));
  79. if(itm){
  80. this.expandoInner.innerHTML = "o";
  81. dojo.addClass(this.domNode, "dojoxGridExpandoLoading");
  82. treeModel.getChildren(itm, function(items){
  83. d._loadedChildren = true;
  84. d._setOpen(open);
  85. });
  86. }else{
  87. this._setOpen(open);
  88. }
  89. }else{
  90. this._setOpen(open);
  91. }
  92. }else if(!treeModel && store){
  93. if(open){
  94. var data = grid._by_idx[this.rowIdx];
  95. if(data&&!store.isItemLoaded(data.item)){
  96. this.expandoInner.innerHTML = "o";
  97. dojo.addClass(this.domNode, "dojoxGridExpandoLoading");
  98. store.loadItem({
  99. item: data.item,
  100. onItem: dojo.hitch(this, function(i){
  101. var idty = store.getIdentity(i);
  102. grid._by_idty[idty] = grid._by_idx[this.rowIdx] = { idty: idty, item: i };
  103. this._setOpen(open);
  104. })
  105. });
  106. }else{
  107. this._setOpen(open);
  108. }
  109. }else{
  110. this._setOpen(open);
  111. }
  112. }else{
  113. this._setOpen(open);
  114. }
  115. },
  116. _setOpen: function(open){
  117. if(open && this._tableRow && dojo.hasClass(this._tableRow, "dojoxGridNoChildren")){
  118. this._setOpen(false);
  119. return;
  120. }
  121. this.expandoInner.innerHTML = open ? "-" : "+";
  122. dojo.removeClass(this.domNode, "dojoxGridExpandoLoading");
  123. dojo.toggleClass(this.domNode, "dojoxGridExpandoOpened", open);
  124. if(this._tableRow){
  125. dojo.toggleClass(this._tableRow, "dojoxGridRowCollapsed", !open);
  126. var base = dojo.attr(this._tableRow, "dojoxTreeGridBaseClasses");
  127. var new_base = "";
  128. if(open){
  129. new_base = dojo.trim((" " + base + " ").replace(" dojoxGridRowCollapsed ", " "));
  130. }else{
  131. if((" " + base + " ").indexOf(' dojoxGridRowCollapsed ') < 0){
  132. new_base = base + (base ? ' ' : '' ) + 'dojoxGridRowCollapsed';
  133. }else{
  134. new_base = base;
  135. }
  136. }
  137. dojo.attr(this._tableRow, 'dojoxTreeGridBaseClasses', new_base);
  138. }
  139. var changed = (this.open !== open);
  140. this.open = open;
  141. if(this.expandoCell && this.itemId){
  142. this.expandoCell.openStates[this.itemId] = open;
  143. }
  144. var v = this.view;
  145. var g = v.grid;
  146. if(this.toggleClass && changed){
  147. if(!this._tableRow || !this._tableRow.style.display){
  148. this._toggleRows(this.toggleClass, open);
  149. }
  150. }
  151. if(v && this._initialized && this.rowIdx >= 0){
  152. g.rowHeightChanged(this.rowIdx);
  153. g.postresize();
  154. v.hasVScrollbar(true);
  155. }
  156. this._initialized = true;
  157. },
  158. onToggle: function(e){
  159. this.setOpen(!this.open);
  160. dojo.stopEvent(e);
  161. },
  162. setRowNode: function(rowIdx, rowNode, view){
  163. if(this.cellIdx < 0 || !this.itemId){ return false; }
  164. this._initialized = false;
  165. this.view = view;
  166. this.rowNode = rowNode;
  167. this.rowIdx = rowIdx;
  168. this.expandoCell = view.structure.cells[0][this.cellIdx];
  169. var d = this.domNode;
  170. if(d && d.parentNode && d.parentNode.parentNode){
  171. this._tableRow = d.parentNode.parentNode;
  172. }
  173. this.open = this.expandoCell.getOpenState(this.itemId);
  174. if(view.grid.treeModel){
  175. // TODO: Rather than hard-code the 18px and 3px, we should probably
  176. // calculate them based off css or something... However, all the
  177. // themes that we support use these values.
  178. dojo.style(this.domNode , "marginLeft" , (this.level * 18) + "px");
  179. if(this.domNode.parentNode){
  180. dojo.style(this.domNode.parentNode, "backgroundPosition", ((this.level * 18) + (3)) + "px");
  181. }
  182. }
  183. this.setOpen(this.open);
  184. return true;
  185. }
  186. });
  187. dojo.declare("dojox.grid._TreeContentBuilder", dojox.grid._ContentBuilder, {
  188. generateHtml: function(inDataIndex, inRowIndex){
  189. var
  190. html = this.getTableArray(),
  191. v = this.view,
  192. row = v.structure.cells[0],
  193. item = this.grid.getItem(inRowIndex),
  194. grid = this.grid,
  195. store = this.grid.store;
  196. dojox.grid.util.fire(this.view, "onBeforeRow", [inRowIndex, [row]]);
  197. var createRow = function(level, rowItem, summaryRow, toggleClasses, rowStack, shown){
  198. if(!shown){
  199. if(html[0].indexOf('dojoxGridRowTableNeedsRowUpdate') == -1){
  200. html[0] = html[0].replace("dojoxGridRowTable", "dojoxGridRowTable dojoxGridRowTableNeedsRowUpdate");
  201. }
  202. return;
  203. }
  204. var rowNodeIdx = html.length;
  205. toggleClasses = toggleClasses || [];
  206. var tcJoin = toggleClasses.join('|');
  207. var tcString = toggleClasses[toggleClasses.length - 1];
  208. var clString = tcString + (summaryRow ? " dojoxGridSummaryRow" : "");
  209. var sString = "";
  210. if(grid.treeModel && rowItem && !grid.treeModel.mayHaveChildren(rowItem)){
  211. clString += " dojoxGridNoChildren";
  212. }
  213. html.push('<tr style="' + sString + '" class="' + clString + '" dojoxTreeGridPath="' + rowStack.join('/') + '" dojoxTreeGridBaseClasses="' + clString + '">');
  214. var nextLevel = level + 1;
  215. var parentCell = null;
  216. for(var i=0, cell; (cell=row[i]); i++){
  217. var m = cell.markup, cc = cell.customClasses = [], cs = cell.customStyles = [];
  218. // content (format can fill in cc and cs as side-effects)
  219. m[5] = cell.formatAtLevel(rowStack, rowItem, level, summaryRow, tcString, cc);
  220. // classes
  221. m[1] = cc.join(' ');
  222. // styles
  223. m[3] = cs.join(';');
  224. // in-place concat
  225. html.push.apply(html, m);
  226. if(!parentCell && cell.level === nextLevel && cell.parentCell){
  227. parentCell = cell.parentCell;
  228. }
  229. }
  230. html.push('</tr>');
  231. if(rowItem && store && store.isItem(rowItem)){
  232. var idty = store.getIdentity(rowItem);
  233. if(typeof grid._by_idty_paths[idty] == "undefined"){
  234. grid._by_idty_paths[idty] = rowStack.join('/');
  235. }
  236. }
  237. var expandoCell;
  238. var parentOpen;
  239. var path;
  240. var values;
  241. var iStack = rowStack.concat([]);
  242. if(grid.treeModel && rowItem){
  243. if(grid.treeModel.mayHaveChildren(rowItem)){
  244. expandoCell = v.structure.cells[0][grid.expandoCell||0];
  245. parentOpen = expandoCell.getOpenState(rowItem) && shown;
  246. path = new dojox.grid.TreePath(rowStack.join('/'), grid);
  247. values = path.children(true)||[];
  248. dojo.forEach(values, function(cItm, idx){
  249. var nToggle = tcJoin.split('|');
  250. nToggle.push(nToggle[nToggle.length - 1] + "-" + idx);
  251. iStack.push(idx);
  252. createRow(nextLevel, cItm, false, nToggle, iStack, parentOpen);
  253. iStack.pop();
  254. });
  255. }
  256. }else if(rowItem && parentCell && !summaryRow){
  257. expandoCell = v.structure.cells[0][parentCell.level];
  258. parentOpen = expandoCell.getOpenState(rowItem) && shown;
  259. if(store.hasAttribute(rowItem, parentCell.field)){
  260. var tToggle = tcJoin.split('|');
  261. tToggle.pop();
  262. path = new dojox.grid.TreePath(rowStack.join('/'), grid);
  263. values = path.children(true)||[];
  264. if(values.length){
  265. html[rowNodeIdx] = '<tr class="' + tToggle.join(' ') +' dojoxGridExpandoRow" dojoxTreeGridPath="' + rowStack.join('/') + '">';
  266. dojo.forEach(values, function(cItm, idx){
  267. var nToggle = tcJoin.split('|');
  268. nToggle.push(nToggle[nToggle.length - 1] + "-" + idx);
  269. iStack.push(idx);
  270. createRow(nextLevel, cItm, false, nToggle, iStack, parentOpen);
  271. iStack.pop();
  272. });
  273. iStack.push(values.length);
  274. createRow(level, rowItem, true, toggleClasses, iStack, parentOpen);
  275. }else{
  276. html[rowNodeIdx] = '<tr class="' + tcString + ' dojoxGridNoChildren" dojoxTreeGridPath="' + rowStack.join('/') + '">';
  277. }
  278. }else{
  279. if(!store.isItemLoaded(rowItem)){
  280. html[0] = html[0].replace("dojoxGridRowTable", "dojoxGridRowTable dojoxGridRowTableNeedsRowUpdate");
  281. }else{
  282. html[rowNodeIdx] = '<tr class="' + tcString + ' dojoxGridNoChildren" dojoxTreeGridPath="' + rowStack.join('/') + '">';
  283. }
  284. }
  285. }else if(rowItem && !summaryRow && toggleClasses.length > 1){
  286. html[rowNodeIdx] = '<tr class="' + toggleClasses[toggleClasses.length - 2] + '" dojoxTreeGridPath="' + rowStack.join('/') + '">';
  287. }
  288. };
  289. createRow(0, item, false, ["dojoxGridRowToggle-" + inRowIndex], [inRowIndex], true);
  290. html.push('</table>');
  291. return html.join(''); // String
  292. },
  293. findTarget: function(inSource, inTag){
  294. var n = inSource;
  295. while(n && (n!=this.domNode)){
  296. if(n.tagName && n.tagName.toLowerCase() == 'tr'){
  297. break;
  298. }
  299. n = n.parentNode;
  300. }
  301. return (n != this.domNode) ? n : null;
  302. },
  303. getCellNode: function(inRowNode, inCellIndex){
  304. var node = dojo.query("td[idx='" + inCellIndex + "']", inRowNode)[0];
  305. if(node&&node.parentNode&&!dojo.hasClass(node.parentNode, "dojoxGridSummaryRow")){
  306. return node;
  307. }
  308. },
  309. decorateEvent: function(e){
  310. e.rowNode = this.findRowTarget(e.target);
  311. if(!e.rowNode){return false;}
  312. e.rowIndex = dojo.attr(e.rowNode, 'dojoxTreeGridPath');
  313. this.baseDecorateEvent(e);
  314. e.cell = this.grid.getCell(e.cellIndex);
  315. return true; // Boolean
  316. }
  317. });
  318. dojo.declare("dojox.grid._TreeView", [dojox.grid._View], {
  319. _contentBuilderClass: dojox.grid._TreeContentBuilder,
  320. _onDndDrop: function(source, nodes, copy){
  321. if(this.grid && this.grid.aggregator){
  322. this.grid.aggregator.clearSubtotalCache();
  323. }
  324. this.inherited(arguments);
  325. },
  326. postCreate: function(){
  327. this.inherited(arguments);
  328. this.connect(this.grid, '_cleanupExpandoCache', '_cleanupExpandoCache');
  329. },
  330. _cleanupExpandoCache: function(index, identity, item){
  331. if(index == -1){
  332. return;
  333. }
  334. dojo.forEach(this.grid.layout.cells, function(cell){
  335. if(typeof cell['openStates'] != 'undefined'){
  336. if(identity in cell.openStates){
  337. delete cell.openStates[identity];
  338. }
  339. }
  340. });
  341. if(typeof index == "string" && index.indexOf('/') > -1){
  342. var path = new dojox.grid.TreePath(index, this.grid);
  343. var ppath = path.parent();
  344. while(ppath){
  345. path = ppath;
  346. ppath = path.parent();
  347. }
  348. var pitem = path.item();
  349. if(!pitem){
  350. return;
  351. }
  352. var idty = this.grid.store.getIdentity(pitem);
  353. if(typeof this._expandos[idty] != 'undefined'){
  354. for(var i in this._expandos[idty]){
  355. var exp = this._expandos[idty][i];
  356. if(exp){
  357. exp.destroy();
  358. }
  359. delete this._expandos[idty][i];
  360. }
  361. delete this._expandos[idty];
  362. }
  363. }else{
  364. for(var i in this._expandos){
  365. if(typeof this._expandos[i] != 'undefined'){
  366. for(var j in this._expandos[i]){
  367. var exp = this._expandos[i][j];
  368. if(exp){
  369. exp.destroy();
  370. }
  371. }
  372. }
  373. }
  374. this._expandos = {};
  375. }
  376. },
  377. postMixInProperties: function(){
  378. this.inherited(arguments);
  379. this._expandos = {};
  380. },
  381. onBeforeRow: function(inRowIndex, cells){
  382. // Save off our expando if we have one so we don't have to create it
  383. // again
  384. var g = this.grid;
  385. if(g._by_idx && g._by_idx[inRowIndex] && g._by_idx[inRowIndex].idty){
  386. var idty = g._by_idx[inRowIndex].idty;
  387. this._expandos[idty] = this._expandos[idty] || {};
  388. }
  389. this.inherited(arguments);
  390. },
  391. onAfterRow: function(inRowIndex, cells, inRowNode){
  392. dojo.forEach(dojo.query("span.dojoxGridExpando", inRowNode), function(n){
  393. if(n && n.parentNode){
  394. // Either create our expando or put the existing expando back
  395. // into place
  396. var tc = n.getAttribute("toggleClass");
  397. var idty;
  398. var expando;
  399. var g = this.grid;
  400. if(g._by_idx && g._by_idx[inRowIndex] && g._by_idx[inRowIndex].idty){
  401. idty = g._by_idx[inRowIndex].idty;
  402. expando = this._expandos[idty][tc];
  403. }
  404. if(expando){
  405. dojo.place(expando.domNode, n, "replace");
  406. expando.itemId = n.getAttribute("itemId");
  407. expando.cellIdx = parseInt(n.getAttribute("cellIdx"), 10);
  408. if(isNaN(expando.cellIdx)){
  409. expando.cellIdx = -1;
  410. }
  411. }else{
  412. if(idty){
  413. expando = dojo.parser.parse(n.parentNode)[0];
  414. this._expandos[idty][tc] = expando;
  415. }
  416. }
  417. if(expando && !expando.setRowNode(inRowIndex, inRowNode, this)){
  418. expando.domNode.parentNode.removeChild(expando.domNode);
  419. }
  420. }
  421. }, this);
  422. var alt = false;
  423. var self = this;
  424. dojo.query("tr[dojoxTreeGridPath]", inRowNode).forEach(function(n){
  425. dojo.toggleClass(n, "dojoxGridSubRowAlt", alt);
  426. dojo.attr(n, "dojoxTreeGridBaseClasses", n.className);
  427. alt = !alt;
  428. self.grid.rows.styleRowNode(dojo.attr(n, 'dojoxTreeGridPath'), n);
  429. });
  430. this.inherited(arguments);
  431. },
  432. updateRowStyles: function(inRowIndex){
  433. var rowNodes = dojo.query("tr[dojoxTreeGridPath='" + inRowIndex + "']", this.domNode);
  434. if(rowNodes.length){
  435. this.styleRowNode(inRowIndex, rowNodes[0]);
  436. }
  437. },
  438. getCellNode: function(inRowIndex, inCellIndex){
  439. var row = dojo.query("tr[dojoxTreeGridPath='" + inRowIndex + "']", this.domNode)[0];
  440. if(row){
  441. return this.content.getCellNode(row, inCellIndex);
  442. }
  443. },
  444. destroy: function(){
  445. this._cleanupExpandoCache();
  446. this.inherited(arguments);
  447. }
  448. });
  449. }