LazyTreeGrid.js 26 KB

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