TreeGrid.js 27 KB

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