TreeGrid.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  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.TreeGrid"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid.TreeGrid"] = true;
  8. dojo.experimental("dojox.grid.TreeGrid");
  9. dojo.provide("dojox.grid.TreeGrid");
  10. dojo.require("dojox.grid.DataGrid");
  11. dojo.require("dojox.grid._TreeView");
  12. dojo.require("dojox.grid.cells.tree");
  13. dojo.require("dojox.grid.TreeSelection");
  14. dojo.declare("dojox.grid._TreeAggregator", null, {
  15. cells: [],
  16. grid: null,
  17. childFields: [],
  18. constructor: function(kwArgs){
  19. this.cells = kwArgs.cells || [];
  20. this.childFields = kwArgs.childFields || [];
  21. this.grid = kwArgs.grid;
  22. this.store = this.grid.store;
  23. },
  24. _cacheValue: function(cache, id, value){
  25. cache[id] = value;
  26. return value;
  27. },
  28. clearSubtotalCache: function(){
  29. // summary:
  30. // Clears the subtotal cache so that we are forced to recalc it
  31. // (or reread it) again. This is needed, for example, when
  32. // column order is changed.
  33. if(this.store){
  34. delete this.store._cachedAggregates;
  35. }
  36. },
  37. cnt: function(cell, level, item){
  38. // summary:
  39. // calculates the count of the children of item at the given level
  40. var total = 0;
  41. var store = this.store;
  42. var childFields = this.childFields;
  43. if(childFields[level]){
  44. var children = store.getValues(item, childFields[level]);
  45. if (cell.index <= level + 1){
  46. total = children.length;
  47. }else{
  48. dojo.forEach(children, function(c){
  49. total += this.getForCell(cell, level + 1, c, "cnt");
  50. }, this);
  51. }
  52. }else{
  53. total = 1;
  54. }
  55. return total;
  56. },
  57. sum: function(cell, level, item){
  58. // summary:
  59. // calculates the sum of the children of item at the given level
  60. var total = 0;
  61. var store = this.store;
  62. var childFields = this.childFields;
  63. if(childFields[level]){
  64. dojo.forEach(store.getValues(item, childFields[level]), function(c){
  65. total += this.getForCell(cell, level + 1, c, "sum");
  66. }, this);
  67. }else{
  68. total += store.getValue(item, cell.field);
  69. }
  70. return total;
  71. },
  72. value: function(cell, level, item){
  73. // summary:
  74. // Empty function so that we can set "aggregate='value'" to
  75. // force loading from the data - and bypass calculating
  76. },
  77. getForCell: function(cell, level, item, type){
  78. // summary:
  79. // Gets the value of the given cell at the given level and type.
  80. // type can be one of "sum", "cnt", or "value". If itemAggregates
  81. // is set and can be used, it is used instead. Values are also
  82. // cached to prevent calculating them too often.
  83. var store = this.store;
  84. if(!store || !item || !store.isItem(item)){ return ""; }
  85. var storeCache = store._cachedAggregates = store._cachedAggregates || {};
  86. var id = store.getIdentity(item);
  87. var itemCache = storeCache[id] = storeCache[id] || [];
  88. if(!cell.getOpenState){
  89. cell = this.grid.getCell(cell.layoutIndex + level + 1);
  90. }
  91. var idx = cell.index;
  92. var idxCache = itemCache[idx] = itemCache[idx] || {};
  93. type = (type || (cell.parentCell ? cell.parentCell.aggregate : "sum"))||"sum";
  94. var attr = cell.field;
  95. if(attr == store.getLabelAttributes()[0]){
  96. // If our attribute is one of the label attributes, we should
  97. // use cnt instead (since it makes no sense to do a sum of labels)
  98. type = "cnt";
  99. }
  100. var typeCache = idxCache[type] = idxCache[type] || [];
  101. // See if we have it in our cache immediately for easy returning
  102. if(typeCache[level] != undefined){
  103. return typeCache[level];
  104. }
  105. // See if they have specified a valid field
  106. var field = ((cell.parentCell && cell.parentCell.itemAggregates) ?
  107. cell.parentCell.itemAggregates[cell.idxInParent] : "")||"";
  108. if(field && store.hasAttribute(item, field)){
  109. return this._cacheValue(typeCache, level, store.getValue(item, field));
  110. }else if(field){
  111. return this._cacheValue(typeCache, level, 0);
  112. }
  113. // Calculate it
  114. return this._cacheValue(typeCache, level, this[type](cell, level, item));
  115. }
  116. });
  117. dojo.declare("dojox.grid._TreeLayout", dojox.grid._Layout, {
  118. // Whether or not we are collapsable - this is calculated when we
  119. // set our structure.
  120. _isCollapsable: false,
  121. _getInternalStructure: function(inStructure){
  122. // Create a "Tree View" with 1 row containing references for
  123. // each column (recursively)
  124. var g = this.grid;
  125. var s = inStructure;
  126. var cells = s[0].cells[0];
  127. var tree = {
  128. type: "dojox.grid._TreeView",
  129. cells: [[]]
  130. };
  131. var cFields = [];
  132. var maxLevels = 0;
  133. var getTreeCells = function(parentCell, level){
  134. var children = parentCell.children;
  135. var cloneTreeCell = function(originalCell, idx){
  136. var k, n = {};
  137. for(k in originalCell){
  138. n[k] = originalCell[k];
  139. }
  140. n = dojo.mixin(n, {
  141. level: level,
  142. idxInParent: level > 0 ? idx : -1,
  143. parentCell: level > 0 ? parentCell : null
  144. });
  145. return n;
  146. };
  147. var ret = [];
  148. dojo.forEach(children, function(c, idx){
  149. if("children" in c){
  150. cFields.push(c.field);
  151. var last = ret[ret.length - 1];
  152. last.isCollapsable = true;
  153. c.level = level;
  154. ret = ret.concat(getTreeCells(c, level + 1));
  155. }else{
  156. ret.push(cloneTreeCell(c, idx));
  157. }
  158. });
  159. maxLevels = Math.max(maxLevels, level);
  160. return ret;
  161. };
  162. var tCell = {children: cells, itemAggregates: []};
  163. tree.cells[0] = getTreeCells(tCell, 0);
  164. g.aggregator = new dojox.grid._TreeAggregator({cells: tree.cells[0],
  165. grid: g,
  166. childFields: cFields});
  167. if(g.scroller && g.defaultOpen){
  168. g.scroller.defaultRowHeight = g.scroller._origDefaultRowHeight * (2 * maxLevels + 1);
  169. }
  170. return [ tree ];
  171. },
  172. setStructure: function(inStructure){
  173. // Mangle the structure a bit and make it work as desired
  174. var s = inStructure;
  175. var g = this.grid;
  176. // Only supporting single-view, single row or else we
  177. // are not collapsable
  178. if(g && g.treeModel && !dojo.every(s, function(i){
  179. return ("cells" in i);
  180. })){
  181. s = arguments[0] = [{cells:[s]}];
  182. }
  183. if(s.length == 1 && s[0].cells.length == 1){
  184. if(g && g.treeModel){
  185. s[0].type = "dojox.grid._TreeView";
  186. this._isCollapsable = true;
  187. s[0].cells[0][(this.grid.treeModel?this.grid.expandoCell:0)].isCollapsable = true;
  188. }else{
  189. var childCells = dojo.filter(s[0].cells[0], function(c){
  190. return ("children" in c);
  191. });
  192. if(childCells.length === 1){
  193. this._isCollapsable = true;
  194. }
  195. }
  196. }
  197. if(this._isCollapsable && (!g || !g.treeModel)){
  198. arguments[0] = this._getInternalStructure(s);
  199. }
  200. this.inherited(arguments);
  201. },
  202. addCellDef: function(inRowIndex, inCellIndex, inDef){
  203. var obj = this.inherited(arguments);
  204. return dojo.mixin(obj, dojox.grid.cells.TreeCell);
  205. }
  206. });
  207. dojo.declare("dojox.grid.TreePath", null, {
  208. level: 0,
  209. _str: "",
  210. _arr: null,
  211. grid: null,
  212. store: null,
  213. cell: null,
  214. item: null,
  215. constructor: function(/*String|Integer[]|Integer|dojox.grid.TreePath*/ path, /*dojox.grid.TreeGrid*/ grid){
  216. if(dojo.isString(path)){
  217. this._str = path;
  218. this._arr = dojo.map(path.split('/'), function(item){ return parseInt(item, 10); });
  219. }else if(dojo.isArray(path)){
  220. this._str = path.join('/');
  221. this._arr = path.slice(0);
  222. }else if(typeof path == "number"){
  223. this._str = String(path);
  224. this._arr = [path];
  225. }else{
  226. this._str = path._str;
  227. this._arr = path._arr.slice(0);
  228. }
  229. this.level = this._arr.length-1;
  230. this.grid = grid;
  231. this.store = this.grid.store;
  232. if(grid.treeModel){
  233. this.cell = grid.layout.cells[grid.expandoCell];
  234. }else{
  235. this.cell = grid.layout.cells[this.level];
  236. }
  237. },
  238. item: function(){
  239. // summary:
  240. // gets the dojo.data item associated with this path
  241. if(!this._item){
  242. this._item = this.grid.getItem(this._arr);
  243. }
  244. return this._item;
  245. },
  246. compare: function(path /*dojox.grid.TreePath|String|Array*/){
  247. // summary:
  248. // compares two paths
  249. if(dojo.isString(path) || dojo.isArray(path)){
  250. if(this._str == path){ return 0; }
  251. if(path.join && this._str == path.join('/')){ return 0; }
  252. path = new dojox.grid.TreePath(path, this.grid);
  253. }else if(path instanceof dojox.grid.TreePath){
  254. if(this._str == path._str){ return 0; }
  255. }
  256. for(var i=0, l=(this._arr.length < path._arr.length ? this._arr.length : path._arr.length); i<l; i++){
  257. if(this._arr[i]<path._arr[i]){ return -1; }
  258. if(this._arr[i]>path._arr[i]){ return 1; }
  259. }
  260. if(this._arr.length<path._arr.length){ return -1; }
  261. if(this._arr.length>path._arr.length){ return 1; }
  262. return 0;
  263. },
  264. isOpen: function(){
  265. // summary:
  266. // Returns the open state of this cell.
  267. return this.cell.openStates && this.cell.getOpenState(this.item());
  268. },
  269. previous: function(){
  270. // summary:
  271. // Returns the path that is before this path in the
  272. // grid. If no path is found, returns null.
  273. var new_path = this._arr.slice(0);
  274. if(this._str == "0"){
  275. return null;
  276. }
  277. var last = new_path.length-1;
  278. if(new_path[last] === 0){
  279. new_path.pop();
  280. return new dojox.grid.TreePath(new_path, this.grid);
  281. }
  282. new_path[last]--;
  283. var path = new dojox.grid.TreePath(new_path, this.grid);
  284. return path.lastChild(true);
  285. },
  286. next: function(){
  287. // summary:
  288. // Returns the next path in the grid. If no path
  289. // is found, returns null.
  290. var new_path = this._arr.slice(0);
  291. if(this.isOpen()){
  292. new_path.push(0);
  293. }else{
  294. new_path[new_path.length-1]++;
  295. for(var i=this.level; i>=0; i--){
  296. var item = this.grid.getItem(new_path.slice(0, i+1));
  297. if(i>0){
  298. if(!item){
  299. new_path.pop();
  300. new_path[i-1]++;
  301. }
  302. }else{
  303. if(!item){
  304. return null;
  305. }
  306. }
  307. }
  308. }
  309. return new dojox.grid.TreePath(new_path, this.grid);
  310. },
  311. children: function(alwaysReturn){
  312. // summary:
  313. // Returns the child data items of this row. If this
  314. // row isn't open and alwaysReturn is falsey, returns null.
  315. if(!this.isOpen()&&!alwaysReturn){
  316. return null;
  317. }
  318. var items = [];
  319. var model = this.grid.treeModel;
  320. if(model){
  321. var item = this.item();
  322. var store = model.store;
  323. if(!model.mayHaveChildren(item)){
  324. return null;
  325. }
  326. dojo.forEach(model.childrenAttrs, function(attr){
  327. items = items.concat(store.getValues(item, attr));
  328. });
  329. }else{
  330. items = this.store.getValues(this.item(), this.grid.layout.cells[this.cell.level+1].parentCell.field);
  331. if(items.length>1&&this.grid.sortChildItems){
  332. var sortProps = this.grid.getSortProps();
  333. if(sortProps&&sortProps.length){
  334. var attr = sortProps[0].attribute,
  335. grid = this.grid;
  336. if(attr&&items[0][attr]){
  337. var desc = !!sortProps[0].descending;
  338. items = items.slice(0); // don't touch the array in the store, make a copy
  339. items.sort(function(a, b){
  340. return grid._childItemSorter(a, b, attr, desc);
  341. });
  342. }
  343. }
  344. }
  345. }
  346. return items;
  347. },
  348. childPaths: function(){
  349. var childItems = this.children();
  350. if(!childItems){
  351. return [];
  352. }
  353. return dojo.map(childItems, function(item, index){
  354. return new dojox.grid.TreePath(this._str + '/' + index, this.grid);
  355. }, this);
  356. },
  357. parent: function(){
  358. // summary:
  359. // Returns the parent path of this path. If this is a
  360. // top-level row, returns null.
  361. if(this.level === 0){
  362. return null;
  363. }
  364. return new dojox.grid.TreePath(this._arr.slice(0, this.level), this.grid);
  365. },
  366. lastChild: function(/*Boolean?*/ traverse){
  367. // summary:
  368. // Returns the last child row below this path. If traverse
  369. // is true, will traverse down to find the last child row
  370. // of this branch. If there are no children, returns itself.
  371. var children = this.children();
  372. if(!children || !children.length){
  373. return this;
  374. }
  375. var path = new dojox.grid.TreePath(this._str + "/" + String(children.length-1), this.grid);
  376. if(!traverse){
  377. return path;
  378. }
  379. return path.lastChild(true);
  380. },
  381. toString: function(){
  382. return this._str;
  383. }
  384. });
  385. dojo.declare("dojox.grid._TreeFocusManager", dojox.grid._FocusManager, {
  386. setFocusCell: function(inCell, inRowIndex){
  387. if(inCell && inCell.getNode(inRowIndex)){
  388. this.inherited(arguments);
  389. }
  390. },
  391. isLastFocusCell: function(){
  392. if(this.cell && this.cell.index == this.grid.layout.cellCount-1){
  393. var path = new dojox.grid.TreePath(this.grid.rowCount-1, this.grid);
  394. path = path.lastChild(true);
  395. return this.rowIndex == path._str;
  396. }
  397. return false;
  398. },
  399. next: function(){
  400. // summary:
  401. // focus next grid cell
  402. if(this.cell){
  403. var row=this.rowIndex, col=this.cell.index+1, cc=this.grid.layout.cellCount-1;
  404. var path = new dojox.grid.TreePath(this.rowIndex, this.grid);
  405. if(col > cc){
  406. var new_path = path.next();
  407. if(!new_path){
  408. col--;
  409. }else{
  410. col = 0;
  411. path = new_path;
  412. }
  413. }
  414. if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
  415. var nextCell = this.grid.getCell(col);
  416. if (!this.isLastFocusCell() && !nextCell.editable){
  417. this._focusifyCellNode(false);
  418. this.cell=nextCell;
  419. this.rowIndex=path._str;
  420. this.next();
  421. return;
  422. }
  423. }
  424. this.setFocusIndex(path._str, col);
  425. }
  426. },
  427. previous: function(){
  428. // summary:
  429. // focus previous grid cell
  430. if(this.cell){
  431. var row=(this.rowIndex || 0), col=(this.cell.index || 0) - 1;
  432. var path = new dojox.grid.TreePath(row, this.grid);
  433. if(col < 0){
  434. var new_path = path.previous();
  435. if(!new_path){
  436. col = 0;
  437. }else{
  438. col = this.grid.layout.cellCount-1;
  439. path = new_path;
  440. }
  441. }
  442. if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells
  443. var prevCell = this.grid.getCell(col);
  444. if (!this.isFirstFocusCell() && !prevCell.editable){
  445. this._focusifyCellNode(false);
  446. this.cell=prevCell;
  447. this.rowIndex=path._str;
  448. this.previous();
  449. return;
  450. }
  451. }
  452. this.setFocusIndex(path._str, col);
  453. }
  454. },
  455. move: function(inRowDelta, inColDelta){
  456. if(this.isNavHeader()){
  457. this.inherited(arguments);
  458. return;
  459. }
  460. if(!this.cell){ return; }
  461. // Handle grid proper.
  462. var sc = this.grid.scroller,
  463. r = this.rowIndex,
  464. rc = this.grid.rowCount-1,
  465. path = new dojox.grid.TreePath(this.rowIndex, this.grid);
  466. if(inRowDelta){
  467. var row;
  468. if(inRowDelta>0){
  469. path = path.next();
  470. row = path._arr[0];
  471. if(row > sc.getLastPageRow(sc.page)){
  472. //need to load additional data, let scroller do that
  473. this.grid.setScrollTop(this.grid.scrollTop+sc.findScrollTop(row)-sc.findScrollTop(r));
  474. }
  475. }else if(inRowDelta<0){
  476. path = path.previous();
  477. row = path._arr[0];
  478. if(row <= sc.getPageRow(sc.page)){
  479. //need to load additional data, let scroller do that
  480. this.grid.setScrollTop(this.grid.scrollTop-sc.findScrollTop(r)-sc.findScrollTop(row));
  481. }
  482. }
  483. }
  484. var cc = this.grid.layout.cellCount-1,
  485. i = this.cell.index,
  486. col = Math.min(cc, Math.max(0, i+inColDelta));
  487. var cell = this.grid.getCell(col);
  488. var colDir = inColDelta < 0 ? -1 : 1;
  489. while(col>=0 && col < cc && cell && cell.hidden === true){
  490. // skip hidden cells
  491. col += colDir;
  492. cell = this.grid.getCell(col);
  493. }
  494. if (!cell || cell.hidden === true){
  495. // don't change col if would move to hidden
  496. col = i;
  497. }
  498. if(inRowDelta){
  499. this.grid.updateRow(r);
  500. }
  501. this.setFocusIndex(path._str, col);
  502. }
  503. });
  504. dojo.declare("dojox.grid.TreeGrid", dojox.grid.DataGrid, {
  505. // summary:
  506. // A grid that supports nesting rows - it provides an expando function
  507. // similar to dijit.Tree. It also provides mechanisms for aggregating
  508. // the values of subrows
  509. //
  510. // description:
  511. // TreeGrid currently only works on "simple" structures. That is,
  512. // single-view structures with a single row in them.
  513. //
  514. // The TreeGrid works using the concept of "levels" - level 0 are the
  515. // top-level items.
  516. // defaultOpen: Boolean
  517. // Whether or not we default to open (all levels). This defaults to
  518. // false for grids with a treeModel.
  519. defaultOpen: true,
  520. // sortChildItems: Boolean
  521. // If true, child items will be returned sorted according to the sorting
  522. // properties of the grid.
  523. sortChildItems: false,
  524. // openAtLevels: Array
  525. // Which levels we are open at (overrides defaultOpen for the values
  526. // that exist here). Its values can be a boolean (true/false) or an
  527. // integer (for the # of children to be closed if there are more than
  528. // that)
  529. openAtLevels: [],
  530. // treeModel: dijit.tree.ForestStoreModel
  531. // A dijit.Tree model that will be used instead of using aggregates.
  532. // Setting this value will make the TreeGrid behave like a columnar
  533. // tree. When setting this value, defaultOpen will default to false,
  534. // and openAtLevels will be ignored.
  535. treeModel: null,
  536. // expandoCell: Integer
  537. // When used in conjunction with a treeModel (see above), this is a 0-based
  538. // index of the cell in which to place the actual expando
  539. expandoCell: 0,
  540. // private values
  541. // aggregator: Object
  542. // The aggregator class - it will be populated automatically if we
  543. // are a collapsable grid
  544. aggregator: null,
  545. // Override this to get our "magic" layout
  546. _layoutClass: dojox.grid._TreeLayout,
  547. createSelection: function(){
  548. this.selection = new dojox.grid.TreeSelection(this);
  549. },
  550. _childItemSorter: function(a, b, attribute, descending){
  551. var av = this.store.getValue(a, attribute);
  552. var bv = this.store.getValue(b, attribute);
  553. if(av != bv){
  554. return av < bv == descending ? 1 : -1;
  555. }
  556. return 0;
  557. },
  558. _onNew: function(item, parentInfo){
  559. if(!parentInfo || !parentInfo.item){
  560. this.inherited(arguments);
  561. }else{
  562. var idx = this.getItemIndex(parentInfo.item);
  563. if(typeof idx == "string"){
  564. this.updateRow(idx.split('/')[0]);
  565. }else if(idx > -1){
  566. this.updateRow(idx);
  567. }
  568. }
  569. },
  570. _onSet: function(item, attribute, oldValue, newValue){
  571. this._checkUpdateStatus();
  572. if(this.aggregator){
  573. this.aggregator.clearSubtotalCache();
  574. }
  575. var idx = this.getItemIndex(item);
  576. if(typeof idx == "string"){
  577. this.updateRow(idx.split('/')[0]);
  578. }else if(idx > -1){
  579. this.updateRow(idx);
  580. }
  581. },
  582. _onDelete: function(item){
  583. this._cleanupExpandoCache(this._getItemIndex(item, true), this.store.getIdentity(item), item);
  584. this.inherited(arguments);
  585. },
  586. _cleanupExpandoCache: function(index, identity, item){},
  587. _addItem: function(item, index, noUpdate, dontUpdateRoot){
  588. // add our root items to the root of the model's children
  589. // list since we don't query the model
  590. if(!dontUpdateRoot && this.model && dojo.indexOf(this.model.root.children, item) == -1){
  591. this.model.root.children[index] = item;
  592. }
  593. this.inherited(arguments);
  594. },
  595. getItem: function(/*integer|Array|String*/ idx){
  596. // summary:
  597. // overridden so that you can pass in a '/' delimited string of indexes to get the
  598. // item based off its path...that is, passing in "1/3/2" will get the
  599. // 3rd (0-based) child from the 4th child of the 2nd top-level item.
  600. var isArray = dojo.isArray(idx);
  601. if(dojo.isString(idx) && idx.indexOf('/')){
  602. idx = idx.split('/');
  603. isArray = true;
  604. }
  605. if(isArray && idx.length == 1){
  606. idx = idx[0];
  607. isArray = false;
  608. }
  609. if(!isArray){
  610. return dojox.grid.DataGrid.prototype.getItem.call(this, idx);
  611. }
  612. var s = this.store;
  613. var itm = dojox.grid.DataGrid.prototype.getItem.call(this, idx[0]);
  614. var cf, i, j;
  615. if(this.aggregator){
  616. cf = this.aggregator.childFields||[];
  617. if(cf){
  618. for(i = 0; i < idx.length - 1 && itm; i++){
  619. if(cf[i]){
  620. itm = (s.getValues(itm, cf[i])||[])[idx[i + 1]];
  621. }else{
  622. itm = null;
  623. }
  624. }
  625. }
  626. }else if(this.treeModel){
  627. cf = this.treeModel.childrenAttrs||[];
  628. if(cf&&itm){
  629. for(i=1, il=idx.length; (i<il) && itm; i++) {
  630. for(j=0, jl=cf.length; j<jl; j++) {
  631. if(cf[j]){
  632. itm = (s.getValues(itm, cf[j])||[])[idx[i]];
  633. }else{
  634. itm = null;
  635. }
  636. if(itm){ break; }
  637. }
  638. }
  639. }
  640. }
  641. return itm || null;
  642. },
  643. _getItemIndex: function(item, isDeleted){
  644. if(!isDeleted && !this.store.isItem(item)){
  645. return -1;
  646. }
  647. var idx = this.inherited(arguments);
  648. if(idx == -1){
  649. var idty = this.store.getIdentity(item);
  650. return this._by_idty_paths[idty] || -1;
  651. }
  652. return idx;
  653. },
  654. postMixInProperties: function(){
  655. if(this.treeModel && !("defaultOpen" in this.params)){
  656. // Default open to false for tree models, true for other tree
  657. // grids.
  658. this.defaultOpen = false;
  659. }
  660. var def = this.defaultOpen;
  661. this.openAtLevels = dojo.map(this.openAtLevels, function(l){
  662. if(typeof l == "string"){
  663. switch(l.toLowerCase()){
  664. case "true":
  665. return true;
  666. break;
  667. case "false":
  668. return false;
  669. break;
  670. default:
  671. var r = parseInt(l, 10);
  672. if(isNaN(r)){
  673. return def;
  674. }
  675. return r;
  676. break;
  677. }
  678. }
  679. return l;
  680. });
  681. this._by_idty_paths = {};
  682. this.inherited(arguments);
  683. },
  684. postCreate: function(){
  685. this.inherited(arguments);
  686. if(this.treeModel){
  687. this._setModel(this.treeModel);
  688. }
  689. },
  690. setModel: function(treeModel){
  691. this._setModel(treeModel);
  692. this._refresh(true);
  693. },
  694. _setModel: function(treeModel){
  695. if(treeModel && (!dijit.tree.ForestStoreModel || !(treeModel instanceof dijit.tree.ForestStoreModel))){
  696. throw new Error("dojox.grid.TreeGrid: treeModel must be an instance of dijit.tree.ForestStoreModel");
  697. }
  698. this.treeModel = treeModel;
  699. dojo.toggleClass(this.domNode, "dojoxGridTreeModel", this.treeModel ? true : false);
  700. this._setQuery(treeModel ? treeModel.query : null);
  701. this._setStore(treeModel ? treeModel.store : null);
  702. },
  703. createScroller: function(){
  704. this.inherited(arguments);
  705. this.scroller._origDefaultRowHeight = this.scroller.defaultRowHeight;
  706. },
  707. createManagers: function(){
  708. // summary:
  709. // create grid managers for various tasks including rows, focus, selection, editing
  710. // row manager
  711. this.rows = new dojox.grid._RowManager(this);
  712. // focus manager
  713. this.focus = new dojox.grid._TreeFocusManager(this);
  714. // edit manager
  715. this.edit = new dojox.grid._EditManager(this);
  716. },
  717. _setStore: function(store){
  718. this.inherited(arguments);
  719. if(this.treeModel&&!this.treeModel.root.children){
  720. this.treeModel.root.children = [];
  721. }
  722. if(this.aggregator){
  723. this.aggregator.store = store;
  724. }
  725. },
  726. getDefaultOpenState: function(cellDef, item){
  727. // summary:
  728. // Returns the default open state for the given definition and item
  729. // It reads from the openAtLevels and defaultOpen values of the
  730. // grid to calculate if the given item should default to open or
  731. // not.
  732. var cf;
  733. var store = this.store;
  734. if(this.treeModel){ return this.defaultOpen; }
  735. if(!cellDef || !store || !store.isItem(item) ||
  736. !(cf = this.aggregator.childFields[cellDef.level])){
  737. return this.defaultOpen;
  738. }
  739. if(this.openAtLevels.length > cellDef.level){
  740. var dVal = this.openAtLevels[cellDef.level];
  741. if(typeof dVal == "boolean"){
  742. return dVal;
  743. }else if(typeof dVal == "number"){
  744. return (store.getValues(item, cf).length <= dVal);
  745. }
  746. }
  747. return this.defaultOpen;
  748. },
  749. onStyleRow: function(row){
  750. if(!this.layout._isCollapsable){
  751. this.inherited(arguments);
  752. return;
  753. }
  754. var base = dojo.attr(row.node, 'dojoxTreeGridBaseClasses');
  755. if(base){
  756. row.customClasses = base;
  757. }
  758. var i = row;
  759. var tagName = i.node.tagName.toLowerCase();
  760. i.customClasses += (i.odd?" dojoxGridRowOdd":"") +
  761. (i.selected&&tagName=='tr'?" dojoxGridRowSelected":"") +
  762. (i.over&&tagName=='tr'?" dojoxGridRowOver":"");
  763. this.focus.styleRow(i);
  764. this.edit.styleRow(i);
  765. },
  766. styleRowNode: function(inRowIndex, inRowNode){
  767. if(inRowNode){
  768. if(inRowNode.tagName.toLowerCase() == 'div' && this.aggregator){
  769. dojo.query("tr[dojoxTreeGridPath]", inRowNode).forEach(function(rowNode){
  770. this.rows.styleRowNode(dojo.attr(rowNode, 'dojoxTreeGridPath'), rowNode);
  771. },this);
  772. }
  773. this.rows.styleRowNode(inRowIndex, inRowNode);
  774. }
  775. },
  776. onCanSelect: function(inRowIndex){
  777. var nodes = dojo.query("tr[dojoxTreeGridPath='" + inRowIndex + "']", this.domNode);
  778. if(nodes.length){
  779. if(dojo.hasClass(nodes[0], 'dojoxGridSummaryRow')){
  780. return false;
  781. }
  782. }
  783. return this.inherited(arguments);
  784. },
  785. onKeyDown: function(e){
  786. if(e.altKey || e.metaKey){
  787. return;
  788. }
  789. var dk = dojo.keys;
  790. switch(e.keyCode){
  791. case dk.UP_ARROW:
  792. if(!this.edit.isEditing() && this.focus.rowIndex != "0"){
  793. dojo.stopEvent(e);
  794. this.focus.move(-1, 0);
  795. }
  796. break;
  797. case dk.DOWN_ARROW:
  798. var currPath = new dojox.grid.TreePath(this.focus.rowIndex, this);
  799. var lastPath = new dojox.grid.TreePath(this.rowCount-1, this);
  800. lastPath = lastPath.lastChild(true);
  801. if(!this.edit.isEditing() && currPath.toString() != lastPath.toString()){
  802. dojo.stopEvent(e);
  803. this.focus.move(1, 0);
  804. }
  805. break;
  806. default:
  807. this.inherited(arguments);
  808. break;
  809. }
  810. },
  811. canEdit: function(inCell, inRowIndex){
  812. var node = inCell.getNode(inRowIndex);
  813. return node && this._canEdit;
  814. },
  815. doApplyCellEdit: function(inValue, inRowIndex, inAttrName){
  816. var item = this.getItem(inRowIndex);
  817. var oldValue = this.store.getValue(item, inAttrName);
  818. if(typeof oldValue == 'number'){
  819. inValue = isNaN(inValue) ? inValue : parseFloat(inValue);
  820. }else if(typeof oldValue == 'boolean'){
  821. inValue = inValue == 'true' ? true : inValue == 'false' ? false : inValue;
  822. }else if(oldValue instanceof Date){
  823. var asDate = new Date(inValue);
  824. inValue = isNaN(asDate.getTime()) ? inValue : asDate;
  825. }
  826. this.store.setValue(item, inAttrName, inValue);
  827. this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
  828. }
  829. });
  830. dojox.grid.TreeGrid.markupFactory = function(props, node, ctor, cellFunc){
  831. var d = dojo;
  832. var widthFromAttr = function(n){
  833. var w = d.attr(n, "width")||"auto";
  834. if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){
  835. w = parseInt(w, 10)+"px";
  836. }
  837. return w;
  838. };
  839. var cellsFromMarkup = function(table){
  840. var rows;
  841. // Don't support colgroup on our grid - single view, single row only
  842. if(table.nodeName.toLowerCase() == "table" &&
  843. d.query("> colgroup", table).length === 0 &&
  844. (rows = d.query("> thead > tr", table)).length == 1){
  845. var tr = rows[0];
  846. return d.query("> th", rows[0]).map(function(th){
  847. // Grab type and field (the only ones that are shared
  848. var cell = {
  849. type: d.trim(d.attr(th, "cellType")||""),
  850. field: d.trim(d.attr(th, "field")||"")
  851. };
  852. if(cell.type){
  853. cell.type = d.getObject(cell.type);
  854. }
  855. var subTable = d.query("> table", th)[0];
  856. if(subTable){
  857. // If we have a subtable, we are an aggregate and a summary cell
  858. cell.name = "";
  859. cell.children = cellsFromMarkup(subTable);
  860. if(d.hasAttr(th, "itemAggregates")){
  861. cell.itemAggregates = d.map(d.attr(th, "itemAggregates").split(","), function(v){
  862. return d.trim(v);
  863. });
  864. }else{
  865. cell.itemAggregates = [];
  866. }
  867. if(d.hasAttr(th, "aggregate")){
  868. cell.aggregate = d.attr(th, "aggregate");
  869. }
  870. cell.type = cell.type || dojox.grid.cells.SubtableCell;
  871. }else{
  872. // Grab our other stuff we need (mostly what's in the normal
  873. // Grid)
  874. cell.name = d.trim(d.attr(th, "name")||th.innerHTML);
  875. if(d.hasAttr(th, "width")){
  876. cell.width = widthFromAttr(th);
  877. }
  878. if(d.hasAttr(th, "relWidth")){
  879. cell.relWidth = window.parseInt(d.attr(th, "relWidth"), 10);
  880. }
  881. if(d.hasAttr(th, "hidden")){
  882. cell.hidden = d.attr(th, "hidden") == "true";
  883. }
  884. cell.field = cell.field||cell.name;
  885. dojox.grid.DataGrid.cell_markupFactory(cellFunc, th, cell);
  886. cell.type = cell.type || dojox.grid.cells.Cell;
  887. }
  888. if(cell.type && cell.type.markupFactory){
  889. cell.type.markupFactory(th, cell);
  890. }
  891. return cell;
  892. });
  893. }
  894. return [];
  895. };
  896. var rows;
  897. if( !props.structure ){
  898. var row = cellsFromMarkup(node);
  899. if(row.length){
  900. // Set our structure here - so that we don't try and set it in the
  901. // markup factory
  902. props.structure = [{__span: Infinity, cells:[row]}];
  903. }
  904. }
  905. return dojox.grid.DataGrid.markupFactory(props, node, ctor, cellFunc);
  906. };
  907. }