LazyTreeGrid.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  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.LazyTreeGrid"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid.LazyTreeGrid"] = true;
  8. dojo.provide("dojox.grid.LazyTreeGrid");
  9. dojo.require("dojox.grid._View");
  10. dojo.require("dojox.grid.TreeGrid");
  11. dojo.require("dojox.grid.cells.tree");
  12. dojo.require("dojox.grid.LazyTreeGridStoreModel");
  13. dojo.declare("dojox.grid._LazyExpando", [dijit._Widget, dijit._Templated], {
  14. itemId: "",
  15. cellIdx: -1,
  16. view: null,
  17. rowIdx: -1,
  18. expandoCell: null,
  19. level: 0,
  20. open: false,
  21. 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",
  22. onToggle: function(event){
  23. // Summary
  24. // Function for expand/collapse row
  25. this.setOpen(!this.view.grid.cache.getExpandoStatusByRowIndex(this.rowIdx));
  26. try{
  27. dojo.stopEvent(event);
  28. }catch(e){}
  29. },
  30. setOpen: function(open){
  31. var g = this.view.grid,
  32. item = g.cache.getItemByRowIndex(this.rowIdx);
  33. if(!g.treeModel.mayHaveChildren(item)){
  34. g.stateChangeNode = null;
  35. return;
  36. }
  37. if(item && !g._loading){
  38. g.stateChangeNode = this.domNode;
  39. g.cache.updateCache(this.rowIdx, {"expandoStatus": open});
  40. g.expandoFetch(this.rowIdx, open);
  41. this.open = open;
  42. }
  43. this._updateOpenState(item);
  44. },
  45. _updateOpenState: function(item){
  46. // Summary
  47. // Update the expando icon
  48. var grid = this.view.grid;
  49. if(item && grid.treeModel.mayHaveChildren(item)){
  50. var state = grid.cache.getExpandoStatusByRowIndex(this.rowIdx);
  51. this.expandoInner.innerHTML = state ? "-" : "+";
  52. dojo.toggleClass(this.domNode, "dojoxGridExpandoOpened", state);
  53. dijit.setWaiState(this.domNode.parentNode, "expanded", state);
  54. }else{
  55. dojo.removeClass(this.domNode, "dojoxGridExpandoOpened");
  56. }
  57. },
  58. setRowNode: function(rowIdx, rowNode, view){
  59. if(this.cellIdx < 0 || !this.itemId){ return false; }
  60. this._initialized = false;
  61. this.view = view;
  62. this.rowIdx = rowIdx;
  63. this.expandoCell = view.structure.cells[0][this.cellIdx];
  64. var d = this.domNode;
  65. if(d && d.parentNode && d.parentNode.parentNode){
  66. this._tableRow = d.parentNode.parentNode;
  67. }
  68. dojo.style(this.domNode , "marginLeft" , (this.level * 1.125) + "em");
  69. this._updateOpenState(view.grid.cache.getItemByRowIndex(this.rowIdx));
  70. return true;
  71. }
  72. });
  73. dojo.declare("dojox.grid._TreeGridContentBuilder", dojox.grid._ContentBuilder, {
  74. // summary:
  75. // Could create row content innerHTML by different appoarch for different data structure
  76. generateHtml: function(inDataIndex, inRowIndex){
  77. // summary:
  78. // create row innterHTML for flat data structure
  79. var html = this.getTableArray(),
  80. grid = this.grid,
  81. v = this.view,
  82. cells = v.structure.cells,
  83. item = grid.getItem(inRowIndex),
  84. level = 0,
  85. treePath = grid.cache.getTreePathByRowIndex(inRowIndex),
  86. rowStack = [],
  87. toggleClasses = [];
  88. dojox.grid.util.fire(this.view, "onBeforeRow", [inRowIndex, cells]);
  89. if(item !== null && treePath !== null){
  90. rowStack = treePath.split("/");
  91. level = rowStack.length - 1;
  92. toggleClasses[0] = "dojoxGridRowToggle-" + rowStack.join("-");
  93. if(!grid.treeModel.mayHaveChildren(item)){
  94. toggleClasses.push("dojoxGridNoChildren");
  95. }
  96. }
  97. for(var j = 0, row; (row = cells[j]); j++){
  98. if(row.hidden || row.header){
  99. continue;
  100. }
  101. var tr = '<tr style="" class="' + toggleClasses.join(' ') + '" dojoxTreeGridPath="' + rowStack.join('/') + '" dojoxTreeGridBaseClasses="' + toggleClasses.join(' ') + '">';
  102. html.push(tr);
  103. var k = 0, mergedCells = this._getColSpans(level);
  104. var totalWidth = 0, totalWidthes = [];
  105. if(mergedCells){
  106. dojo.forEach(mergedCells, function(c){
  107. for(var i = 0, cell;(cell = row[i]); i++){
  108. if(i >= c.start && i <= c.end){
  109. totalWidth += this._getCellWidth(row, i);
  110. }
  111. }
  112. totalWidthes.push(totalWidth);
  113. totalWidth = 0;
  114. }, this);
  115. }
  116. for(var i = 0, cell, m, cc, cs; (cell = row[i]); i++){
  117. m = cell.markup;
  118. cc = cell.customClasses = [];
  119. cs = cell.customStyles = [];
  120. if(mergedCells && mergedCells[k] && (i >= mergedCells[k].start && i <= mergedCells[k].end)){
  121. var primaryIdx = mergedCells[k].primary ? mergedCells[k].primary : mergedCells[k].start;
  122. if(i === primaryIdx){
  123. m[5] = cell.formatAtLevel(rowStack, item, level, false, toggleClasses[0], cc, inRowIndex);
  124. // classes
  125. m[1] = cc.join(' ');
  126. // styles
  127. var pbm = dojo.marginBox(cell.getHeaderNode()).w - dojo.contentBox(cell.getHeaderNode()).w;
  128. cs = cell.customStyles = ['width:' + (totalWidthes[k] - pbm) + "px"];
  129. m[3] = cs.join(';');
  130. html.push.apply(html, m);
  131. }else if(i === mergedCells[k].end){
  132. k++;
  133. continue;
  134. }else{
  135. continue;
  136. }
  137. }else{
  138. // content (format can fill in cc and cs as side-effects)
  139. // m[5] = cell.format(inRowIndex, item);
  140. m[5] = cell.formatAtLevel(rowStack, item, level, false, toggleClasses[0], cc, inRowIndex);
  141. // classes
  142. m[1] = cc.join(' ');
  143. // styles
  144. m[3] = cs.join(';');
  145. // in-place concat
  146. html.push.apply(html, m);
  147. }
  148. }
  149. html.push('</tr>');
  150. }
  151. html.push('</table>');
  152. return html.join(''); // String
  153. },
  154. _getColSpans: function(level){
  155. // summary:
  156. // handle the column span object
  157. var colSpans = this.grid.colSpans;
  158. if(colSpans && (colSpans[level])){
  159. return colSpans[level];
  160. }else{
  161. return null;
  162. }
  163. },
  164. _getCellWidth: function(cells, colIndex){
  165. // summary:
  166. // calculate column width by header cell's size
  167. var curCell = cells[colIndex], node = curCell.getHeaderNode();
  168. if(curCell.hidden){
  169. return 0;
  170. }
  171. if(colIndex == cells.length - 1 || dojo.every(cells.slice(colIndex + 1), function(cell){
  172. return cell.hidden;
  173. })){
  174. var headerNodePos = dojo.position(cells[colIndex].view.headerContentNode.firstChild);
  175. return headerNodePos.x + headerNodePos.w - dojo.position(node).x;
  176. }else{
  177. var nextCell;
  178. do{
  179. nextCell = cells[++colIndex];
  180. }while(nextCell.hidden);
  181. return dojo.position(nextCell.getHeaderNode()).x - dojo.position(node).x;
  182. }
  183. }
  184. });
  185. dojo.declare("dojox.grid._TreeGridView", [dojox.grid._View], {
  186. _contentBuilderClass: dojox.grid._TreeGridContentBuilder,
  187. postCreate: function(){
  188. this.inherited(arguments);
  189. this._expandos = {};
  190. this.connect(this.grid, '_cleanupExpandoCache', '_cleanupExpandoCache');
  191. },
  192. _cleanupExpandoCache: function(index, identity, item){
  193. if(index === -1){
  194. return;
  195. }
  196. dojo.forEach(this.grid.layout.cells, function(cell){
  197. if(cell.openStates && cell.openStates[identity]){
  198. delete cell.openStates[identity];
  199. }
  200. });
  201. for(var i in this._expandos){
  202. if(this._expandos[i]){
  203. this._expandos[i].destroy();
  204. }
  205. }
  206. this._expandos = {};
  207. },
  208. onAfterRow: function(inRowIndex, cells, inRowNode){
  209. // summary:
  210. // parse the expando of each row to a widget
  211. dojo.query("span.dojoxGridExpando", inRowNode).forEach(function(n){
  212. if(n && n.parentNode){
  213. // Either create our expando or put the existing expando back
  214. // into place
  215. var idty, expando, _byIdx = this.grid._by_idx;
  216. if(_byIdx && _byIdx[inRowIndex] && _byIdx[inRowIndex].idty){
  217. idty = _byIdx[inRowIndex].idty;
  218. expando = this._expandos[idty];
  219. }
  220. if(expando){
  221. dojo.place(expando.domNode, n, "replace");
  222. expando.itemId = n.getAttribute("itemId");
  223. expando.cellIdx = parseInt(n.getAttribute("cellIdx"), 10);
  224. if(isNaN(expando.cellIdx)){
  225. expando.cellIdx = -1;
  226. }
  227. }else{
  228. expando = dojo.parser.parse(n.parentNode)[0];
  229. if(idty){
  230. this._expandos[idty] = expando;
  231. }
  232. }
  233. if(!expando.setRowNode(inRowIndex, inRowNode, this)){
  234. expando.domNode.parentNode.removeChild(expando.domNode);
  235. }
  236. dojo.destroy(n);
  237. }
  238. }, this);
  239. this.inherited(arguments);
  240. }
  241. });
  242. dojox.grid.cells.LazyTreeCell = dojo.mixin(dojo.clone(dojox.grid.cells.TreeCell), {
  243. formatAtLevel: function(inRowIndexes, inItem, level, summaryRow, toggleClass, cellClasses, inRowIndex){
  244. if(!inItem){
  245. return this.formatIndexes(inRowIndex, inRowIndexes, inItem, level);
  246. }
  247. if(!dojo.isArray(inRowIndexes)){
  248. inRowIndexes = [inRowIndexes];
  249. }
  250. var result = "";
  251. var ret = "";
  252. if(this.isCollapsable){
  253. var store = this.grid.store, id = "";
  254. if(inItem && store.isItem(inItem)){
  255. id = store.getIdentity(inItem);
  256. }
  257. cellClasses.push("dojoxGridExpandoCell");
  258. ret = '<span ' + dojo._scopeName + 'Type="dojox.grid._LazyExpando" level="' + level + '" class="dojoxGridExpando"' +
  259. '" toggleClass="' + toggleClass + '" itemId="' + id + '" cellIdx="' + this.index + '"></span>';
  260. }
  261. var content = this.formatIndexes(inRowIndex, inRowIndexes, inItem, level);
  262. result = ret !== "" ? '<div>' + ret + content + '</div>' : content;
  263. if(this.grid.focus.cell && this.index === this.grid.focus.cell.index && inRowIndexes.join('/') === this.grid.focus.rowIndex){
  264. cellClasses.push(this.grid.focus.focusClass);
  265. }
  266. return result;
  267. },
  268. formatIndexes: function(inRowIndex, inRowIndexes, inItem, level){
  269. var info = this.grid.edit.info,
  270. d = this.get ? this.get(inRowIndexes[0], inItem, inRowIndexes) : (this.value || this.defaultValue);
  271. if(this.editable && (this.alwaysEditing || (info.rowIndex === inRowIndexes[0] && info.cell === this))){
  272. return this.formatEditing(d, inRowIndex, inRowIndexes);
  273. }else{
  274. return this._defaultFormat(d, [d, inRowIndex, level, this]);
  275. }
  276. }
  277. });
  278. dojo.declare("dojox.grid._LazyTreeLayout", dojox.grid._Layout, {
  279. // summary:
  280. // Override the dojox.grid._TreeLayout to modify the _TreeGridView and cell formatter
  281. setStructure: function(inStructure){
  282. var s = inStructure;
  283. var g = this.grid;
  284. if(g && !dojo.every(s, function(i){
  285. return ("cells" in i);
  286. })){
  287. s = arguments[0] = [{cells:[s]}];//intentionally change arguments[0]
  288. }
  289. if(s.length === 1 && s[0].cells.length === 1){
  290. s[0].type = "dojox.grid._TreeGridView";
  291. this._isCollapsable = true;
  292. s[0].cells[0][this.grid.expandoCell].isCollapsable = true;
  293. }
  294. this.inherited(arguments);
  295. },
  296. addCellDef: function(inRowIndex, inCellIndex, inDef){
  297. var obj = this.inherited(arguments);
  298. return dojo.mixin(obj, dojox.grid.cells.LazyTreeCell);
  299. }
  300. });
  301. dojo.declare("dojox.grid.TreeGridItemCache", null, {
  302. unInit: true,
  303. items: null,
  304. constructor: function(grid){
  305. this.rowsPerPage = grid.rowsPerPage;
  306. this._buildCache(grid.rowsPerPage);
  307. },
  308. _buildCache: function(size){
  309. // Summary
  310. // Build the cache only with the treepath using given size
  311. this.items = [];
  312. for(var i = 0; i < size; i++){
  313. this.cacheItem(i, {item: null, treePath: i + "", expandoStatus: false});
  314. }
  315. },
  316. cacheItem: function(/*integer*/rowIdx, cacheObj){
  317. // Summary
  318. // Add an item and its tree structure information to the cache.
  319. this.items[rowIdx] = dojo.mixin({
  320. item: null,
  321. treePath: "",
  322. expandoStatus: false
  323. }, cacheObj);
  324. },
  325. insertItem: function(/*integer*/rowIdx, cacheObj){
  326. this.items.splice(rowIdx, 0, dojo.mixin({
  327. item: null,
  328. treePath: "",
  329. expandoStatus: false
  330. }, cacheObj));
  331. },
  332. initCache: function(size){
  333. if(!this.unInit){ return; }
  334. this._buildCache(size);
  335. this.unInit = false;
  336. },
  337. getItemByRowIndex: function(/*integer*/rowIdx){
  338. return this.items[rowIdx] ? this.items[rowIdx].item : null;
  339. },
  340. getItemByTreePath: function(treePath){
  341. for(var i = 0, len = this.items.length; i < len; i++){
  342. if(this.items[i].treePath === treePath){
  343. return this.items[i].item;
  344. }
  345. }
  346. return null;
  347. },
  348. getTreePathByRowIndex: function(/*integer*/rowIdx){
  349. return this.items[rowIdx] ? this.items[rowIdx].treePath : null;
  350. },
  351. getExpandoStatusByRowIndex: function(/*integer*/rowIdx){
  352. return this.items[rowIdx] ? this.items[rowIdx].expandoStatus : null;
  353. },
  354. getInfoByItem: function(item){
  355. for(var i = 0, len = this.items.length; i < len; i++){
  356. if(this.items[i].item === item){
  357. return dojo.mixin({rowIdx: i}, this.items[i]);
  358. }
  359. }
  360. return null;
  361. },
  362. updateCache: function(/*integer*/rowIdx, cacheObj){
  363. if(this.items[rowIdx]){
  364. dojo.mixin(this.items[rowIdx], cacheObj);
  365. }
  366. },
  367. deleteItem: function(rowIdx){
  368. if(this.items[rowIdx]){
  369. var treePath = this.items[rowIdx].treePath,
  370. i = rowIdx, indexes,
  371. parentTreePath = treePath.indexOf('/') > 0 ? treePath.substring(0, treePath.lastIndexOf("/") + 1) : "";
  372. for(; i < this.items.length; i++){
  373. if(this.items[i].treePath.indexOf(parentTreePath + '/') == 0){
  374. indexes = this.items[i].treePath.substring(parentTreePath.length).split('/');
  375. indexes[0] = parseInt(indexes[0], 10) - 1;
  376. this.updateCache(i, {treePath: parentTreePath + indexes.join('/')});
  377. }else{
  378. break;
  379. }
  380. }
  381. this.items.splice(rowIdx, 1);
  382. }
  383. },
  384. cleanChildren: function(rowIdx){
  385. var treePath = this.getTreePathByRowIndex(rowIdx);
  386. var childNum = 0, i = this.items.length - 1;
  387. for(; i >= 0; i--){
  388. if(this.items[i].treePath.indexOf(treePath + '/') === 0 && this.items[i].treePath !== treePath){
  389. this.items.splice(i, 1);
  390. childNum++;
  391. }
  392. }
  393. return childNum;
  394. },
  395. emptyCache: function(){
  396. this.unInit = true;
  397. this._buildCache(this.rowsPerPage);
  398. },
  399. cleanupCache: function(){
  400. this.items = null;
  401. }
  402. });
  403. dojo.declare("dojox.grid.LazyTreeGrid", dojox.grid.TreeGrid, {
  404. // summary:
  405. // An enhanced TreeGrid widget which supports lazy-loading nested-level items
  406. //
  407. // description:
  408. // LazyTreeGrid inherits from dojo.grid.TreeGrid, and applies virtual scrolling mechanism
  409. // to nested children rows so that it's possible to deal with large data set specifically
  410. // in tree structure with large number of children rows. It's also compatible with dijit.tree.ForestStoreModel
  411. //
  412. // Most methods and properties pertaining to the dojox.grid.DataGrid
  413. // and dojox.grid.TreeGrid also apply here
  414. //
  415. // LazyTreeGrid does not support summary row/items aggregate for the
  416. // lazy-loading reason.
  417. treeModel: null,
  418. _layoutClass: dojox.grid._LazyTreeLayout,
  419. // colSpans: Object
  420. // a json object that defines column span of each level rows
  421. // attributes:
  422. // 0/1/..: which level need to colspan
  423. // start: start column index of colspan
  424. // end: end column index of colspan
  425. // primary: index of column which content will be displayed (default is value of start).
  426. // example:
  427. // | colSpans = {
  428. // | 0: [
  429. // | {start: 0, end: 1, primary: 0},
  430. // | {start: 2, end: 4, primary: 3}
  431. // | ],
  432. // | 1: [
  433. // | {start: 0, end: 3, primary: 1}
  434. // | ]
  435. // | };
  436. colSpans: null,
  437. postCreate: function(){
  438. this.inherited(arguments);
  439. this.cache = new dojox.grid.TreeGridItemCache(this);
  440. if(!this.treeModel || !(this.treeModel instanceof dijit.tree.ForestStoreModel)){
  441. throw new Error("dojox.grid.LazyTreeGrid: must use a treeModel and treeModel must be an instance of dijit.tree.ForestStoreModel");
  442. }
  443. dojo.addClass(this.domNode, "dojoxGridTreeModel");
  444. dojo.setSelectable(this.domNode, this.selectable);
  445. },
  446. createManagers: function(){
  447. // summary:
  448. // create grid managers for various tasks including rows, focus, selection, editing
  449. this.rows = new dojox.grid._RowManager(this);
  450. this.focus = new dojox.grid._FocusManager(this);
  451. this.edit = new dojox.grid._EditManager(this);
  452. },
  453. createSelection: function(){
  454. this.selection = new dojox.grid.DataSelection(this);
  455. },
  456. setModel: function(treeModel){
  457. if(!treeModel){
  458. return;
  459. }
  460. this._setModel(treeModel);
  461. this._cleanup();
  462. this._refresh(true);
  463. },
  464. setStore: function(store, query, queryOptions){
  465. if(!store){
  466. return;
  467. }
  468. this._setQuery(query, queryOptions);
  469. this.treeModel.query = query;
  470. this.treeModel.store = store;
  471. this.treeModel.root.children = [];
  472. this.setModel(this.treeModel);
  473. },
  474. _setQuery: function(query, queryOptions){
  475. this.inherited(arguments);
  476. this.treeModel.query = query;
  477. },
  478. destroy: function(){
  479. this._cleanup();
  480. this.inherited(arguments);
  481. },
  482. _cleanup: function(){
  483. this.cache.emptyCache();
  484. this._cleanupExpandoCache();
  485. },
  486. setSortIndex: function(inIndex, inAsc){
  487. // Need to clean up the cache before sorting
  488. if(this.canSort(inIndex + 1)){
  489. this._cleanup();
  490. }
  491. this.inherited(arguments);
  492. },
  493. _refresh: function(isRender){
  494. this._clearData();
  495. this.updateRowCount(this.cache.items.length);
  496. this._fetch(0, true);
  497. },
  498. _updateChangedRows: function(start){
  499. dojo.forEach(this.scroller.stack, function(p){
  500. if(p * this.rowsPerPage >= start){
  501. this.updateRows(p * this.rowsPerPage, this.rowsPerPage);
  502. }else if((p + 1) * this.rowsPerPage >= start){
  503. this.updateRows(start, (p + 1) * this.rowsPerPage - start + 1);
  504. }
  505. }, this);
  506. },
  507. render: function(){
  508. this.inherited(arguments);
  509. this.setScrollTop(this.scrollTop);
  510. },
  511. _onNew: function(item, parentInfo){
  512. var isAddingChild = false,
  513. info,
  514. items = this.cache.items;
  515. if(parentInfo && this.store.isItem(parentInfo.item) && dojo.some(this.treeModel.childrenAttrs, function(c){
  516. return c === parentInfo.attribute;
  517. })){
  518. isAddingChild = true;
  519. info = this.cache.getInfoByItem(parentInfo.item);
  520. }
  521. if(!isAddingChild){
  522. this.inherited(arguments);
  523. var treePath = items.length > 0 ? String(parseInt(items[items.length - 1].treePath.split("/")[0], 10) + 1) : "0";
  524. this.cache.insertItem(this.get('rowCount'), {item: item, treePath: treePath, expandoStatus: false});
  525. }else if(info && info.expandoStatus && info.rowIdx >= 0){
  526. // update cache
  527. var childrenNum = info.childrenNum;
  528. var childTreePath = info.treePath + "/" + childrenNum;
  529. var childItem = {item: item, treePath: childTreePath, expandoStatus: false};
  530. var index = info.rowIdx + 1;
  531. for(; index < this.cache.items.length; index++){
  532. if(!this.cache.items[index] || this.cache.items[index].treePath.indexOf(info.treePath + "/") != 0){
  533. break;
  534. }
  535. }
  536. this.cache.insertItem(index, childItem);
  537. this.cache.updateCache(info.rowIdx, {childrenNum: childrenNum + 1});
  538. // update grid._by_idx
  539. var idty = this.store.getIdentity(item);
  540. this._by_idty[idty] = { idty: idty, item: item };
  541. this._by_idx.splice(index, 0, this._by_idty[idty]);
  542. // update grid
  543. this.updateRowCount(items.length);
  544. this._updateChangedRows(index);
  545. }else if(info && info.rowIdx >= 0){
  546. this.updateRow(info.rowIdx);
  547. }
  548. },
  549. _onDelete: function(item){
  550. var info = this.cache.getInfoByItem(item), i;
  551. if(info && info.rowIdx >= 0){
  552. if(info.expandoStatus){
  553. var num = this.cache.cleanChildren(info.rowIdx);
  554. this._by_idx.splice(info.rowIdx + 1, num);
  555. }
  556. if(info.treePath.indexOf("/") > 0){
  557. var parentTreePath = info.treePath.substring(0, info.treePath.lastIndexOf("/"));
  558. for(i = info.rowIdx; i >=0; i--){
  559. if(this.cache.items[i].treePath === parentTreePath){
  560. this.cache.items[i].childrenNum--;
  561. break;
  562. }
  563. }
  564. }
  565. this.cache.deleteItem(info.rowIdx);
  566. this._by_idx.splice(info.rowIdx, 1);
  567. this.updateRowCount(this.cache.items.length);
  568. this._updateChangedRows(info.rowIdx);
  569. }
  570. },
  571. _cleanupExpandoCache: function(index, identity, item){},
  572. _fetch: function(start, isRender){
  573. // summary:
  574. // Function for fetch data when initializing TreeGrid and
  575. // scroll the TreeGrid
  576. if(!this._loading){
  577. this._loading = true;
  578. }
  579. start = start || 0;
  580. this.reqQueue = [];
  581. // Check cache, do not need to fetch data if there are required data in cache
  582. var i = 0, fetchedItems = [];
  583. var count = Math.min(this.rowsPerPage, this.cache.items.length - start);
  584. for(i = start; i < start + count; i++){
  585. if(this.cache.getItemByRowIndex(i)){
  586. fetchedItems.push(this.cache.getItemByRowIndex(i));
  587. }else{
  588. break;
  589. }
  590. }
  591. if(fetchedItems.length === count){// || !this.cache.getTreePathByRowIndex(start + fetchedItems.length)){
  592. this._reqQueueLen = 1;
  593. this._onFetchBegin(this.cache.items.length, {startRowIdx: start, count: count});
  594. this._onFetchComplete(fetchedItems, {startRowIdx: start, count: count});
  595. }else{
  596. // In case there need different level data, we need to do multiple fetch.
  597. // Do next fetch only when the last request complete.
  598. this.reqQueueIndex = 0;
  599. var level = "",
  600. nextRowLevel = "",
  601. startRowIdx = start,
  602. startTreePath = this.cache.getTreePathByRowIndex(start);
  603. count = 0;
  604. // Create request queue
  605. for(i = start + 1; i < start + this.rowsPerPage; i++){
  606. if(!this.cache.getTreePathByRowIndex(i)){
  607. break;
  608. }
  609. level = this.cache.getTreePathByRowIndex(i - 1).split("/").length - 1;
  610. nextRowLevel = this.cache.getTreePathByRowIndex(i).split("/").length - 1;
  611. if(level !== nextRowLevel){
  612. this.reqQueue.push({
  613. startTreePath: startTreePath,
  614. startRowIdx: startRowIdx,
  615. count: count + 1
  616. });
  617. count = 0;
  618. startRowIdx = i;
  619. startTreePath = this.cache.getTreePathByRowIndex(i);
  620. }else{
  621. count++;
  622. }
  623. }
  624. this.reqQueue.push({
  625. startTreePath: startTreePath,
  626. startRowIdx: startRowIdx,
  627. count: count + 1
  628. });
  629. this._reqQueueLen = this.reqQueue.length;
  630. for(i = 0; i < this.reqQueue.length; i++){
  631. this._fetchItems(i, dojo.hitch(this, "_onFetchBegin"), dojo.hitch(this, "_onFetchComplete"), dojo.hitch(this, "_onFetchError"));
  632. }
  633. }
  634. },
  635. _fetchItems: function(idx, onBegin, onComplete, onError){
  636. if(this._pending_requests[this.reqQueue[idx].startRowIdx]){
  637. return;
  638. }
  639. this.showMessage(this.loadingMessage);
  640. var level = this.reqQueue[idx].startTreePath.split("/").length - 1;
  641. this._pending_requests[this.reqQueue[idx].startRowIdx] = true;
  642. if(level === 0){
  643. this.store.fetch({
  644. start: parseInt(this.reqQueue[idx].startTreePath, 10),
  645. startRowIdx: this.reqQueue[idx].startRowIdx,
  646. count: this.reqQueue[idx].count,
  647. query: this.query,
  648. sort: this.getSortProps(),
  649. queryOptions: this.queryOptions,
  650. onBegin: onBegin,
  651. onComplete: onComplete,
  652. onError: onError
  653. });
  654. }else{
  655. var startTreePath = this.reqQueue[idx].startTreePath;
  656. var parentTreePath = startTreePath.substring(0, startTreePath.lastIndexOf("/"));
  657. var startIdx = startTreePath.substring(startTreePath.lastIndexOf("/") + 1);
  658. var parentItem = this.cache.getItemByTreePath(parentTreePath);
  659. if(!parentItem){
  660. throw new Error("Lazy loading TreeGrid on fetch error:");
  661. }
  662. var parentId = this.store.getIdentity(parentItem);
  663. var queryObj = {
  664. start: parseInt(startIdx, 10),
  665. startRowIdx: this.reqQueue[idx].startRowIdx,
  666. count: this.reqQueue[idx].count,
  667. parentId: parentId,
  668. sort: this.getSortProps()
  669. };
  670. var _this = this;
  671. var onFetchComplete = function(){
  672. if(arguments.length == 1){
  673. onComplete.apply(_this, [arguments[0], queryObj]);
  674. }else{
  675. onComplete.apply(_this, arguments);
  676. }
  677. };
  678. this.treeModel.getChildren(parentItem, onFetchComplete, onError, queryObj);
  679. }
  680. },
  681. _onFetchBegin: function(size, request){
  682. this.cache.initCache(size);
  683. size = this.cache.items.length;
  684. this.inherited(arguments);
  685. },
  686. filter: function(query, reRender){
  687. this.cache.emptyCache();
  688. this.inherited(arguments);
  689. },
  690. _onFetchComplete: function(items, request, size){
  691. var treePath = "",
  692. startRowIdx = request.startRowIdx,
  693. count = request.count,
  694. start = items.length <= count ? 0: request.start;
  695. if(items && items.length > 0){
  696. for(var i = 0; i < count; i++){
  697. treePath = this.cache.getTreePathByRowIndex(startRowIdx + i);
  698. if(treePath){
  699. if(!this.cache.getItemByRowIndex(startRowIdx + i)){
  700. this.cache.cacheItem(startRowIdx + i, {
  701. item: items[start + i],
  702. treePath: treePath,
  703. expandoStatus: this.cache.getExpandoStatusByRowIndex(startRowIdx + i)
  704. });
  705. }
  706. }
  707. }
  708. // Add items when all request complete
  709. if(!this.scroller){
  710. return;
  711. }
  712. var len = Math.min(count, items.length);
  713. for(i = 0; i < len; i++){
  714. this._addItem(items[start + i], startRowIdx + i, true);
  715. }
  716. this.updateRows(startRowIdx, len);
  717. }
  718. if(!this.cache.items.length){
  719. this.showMessage(this.noDataMessage);
  720. }else{
  721. this.showMessage();
  722. }
  723. this._pending_requests[startRowIdx] = false;
  724. this._reqQueueLen--;
  725. if(this._loading && this._reqQueueLen === 0){
  726. this._loading = false;
  727. if(this._lastScrollTop){
  728. this.setScrollTop(this._lastScrollTop);
  729. }
  730. }
  731. },
  732. expandoFetch: function(rowIndex, open){
  733. // summary:
  734. // Function for fetch children of a given row
  735. if(this._loading){return;}
  736. this._loading = true;
  737. this.toggleLoadingClass(true);
  738. var item = this.cache.getItemByRowIndex(rowIndex);
  739. this.expandoRowIndex = rowIndex;
  740. this._pages = [];
  741. if(open){
  742. var parentId = this.store.getIdentity(item);
  743. var queryObj = {
  744. start: 0,
  745. count: this.rowsPerPage,
  746. parentId: parentId,
  747. sort: this.getSortProps()
  748. };
  749. this.treeModel.getChildren(item, dojo.hitch(this, "_onExpandoComplete"), dojo.hitch(this, "_onFetchError"), queryObj);
  750. }else{
  751. // get the whole children number when clear the children from cache
  752. var num = this.cache.cleanChildren(rowIndex);
  753. // remove the items from grid._by_idx
  754. this._by_idx.splice(rowIndex + 1, num);
  755. this._bop = this._eop = -1;
  756. //update grid
  757. this.updateRowCount(this.cache.items.length);
  758. this._updateChangedRows(rowIndex + 1);
  759. this.toggleLoadingClass(false);
  760. if(this._loading){
  761. this._loading = false;
  762. }
  763. this.focus._delayedCellFocus();
  764. }
  765. },
  766. _onExpandoComplete: function(childItems, request, size){
  767. var parentTreePath = this.cache.getTreePathByRowIndex(this.expandoRowIndex);
  768. if(size && !isNaN(parseInt(size, 10))){
  769. size = parseInt(size, 10);
  770. }else{
  771. size = childItems.length;
  772. }
  773. var i, j = 0, len = this._by_idx.length;
  774. for(i = this.expandoRowIndex + 1; j < size; i++, j++){
  775. this.cache.insertItem(i, {
  776. item: null,
  777. treePath: parentTreePath + "/" + j,
  778. expandoStatus: false
  779. });
  780. }
  781. this.updateRowCount(this.cache.items.length);
  782. this.cache.updateCache(this.expandoRowIndex, {childrenNum: size});
  783. for(i = 0; i < size; i++){
  784. this.cache.updateCache(this.expandoRowIndex + 1 + i, {item: childItems[i]});
  785. }
  786. // insert NULL to grid._by_idx
  787. for(i = 0; i < size; i++){
  788. this._by_idx.splice(this.expandoRowIndex + 1 + i, 0, null);
  789. }
  790. // insert the fetched items to grid._by_idx
  791. for(i = 0; i < Math.min(size, this.rowsPerPage); i++){
  792. var idty = this.store.getIdentity(childItems[i]);
  793. this._by_idty[idty] = { idty: idty, item: childItems[i] };
  794. this._by_idx.splice(this.expandoRowIndex + 1 + i, 1, this._by_idty[idty]);
  795. }
  796. this._updateChangedRows(this.expandoRowIndex + 1);
  797. this.toggleLoadingClass(false);
  798. this.stateChangeNode = null;
  799. if(this._loading){
  800. this._loading = false;
  801. }
  802. // correct focus
  803. this.focus._delayedCellFocus();
  804. },
  805. toggleLoadingClass: function(flag){
  806. // summary:
  807. // set loading class when expanding/collapsing
  808. if(this.stateChangeNode){
  809. dojo.toggleClass(this.stateChangeNode, "dojoxGridExpandoLoading", flag);
  810. }
  811. },
  812. styleRowNode: function(inRowIndex, inRowNode){
  813. if(inRowNode){
  814. this.rows.styleRowNode(inRowIndex, inRowNode);
  815. }
  816. },
  817. onStyleRow: function(row){
  818. if(!this.layout._isCollapsable){
  819. this.inherited(arguments);
  820. return;
  821. }
  822. var base = dojo.attr(row.node, 'dojoxTreeGridBaseClasses');
  823. if(base){
  824. row.customClasses = base;
  825. }
  826. var i = row;
  827. i.customClasses += (i.odd ? " dojoxGridRowOdd" : "") + (i.selected ? " dojoxGridRowSelected" : "") + (i.over ? " dojoxGridRowOver" : "");
  828. this.focus.styleRow(i);
  829. this.edit.styleRow(i);
  830. },
  831. dokeydown: function(e){
  832. if(e.altKey || e.metaKey){
  833. return;
  834. }
  835. var dk = dojo.keys,
  836. expando = dijit.findWidgets(e.target)[0];
  837. if(e.keyCode === dk.ENTER && expando instanceof dojox.grid._LazyExpando){
  838. expando.onToggle();
  839. }
  840. this.onKeyDown(e);
  841. }
  842. });
  843. dojox.grid.LazyTreeGrid.markupFactory = function(props, node, ctor, cellFunc){
  844. return dojox.grid.TreeGrid.markupFactory(props, node, ctor, cellFunc);
  845. };
  846. }