source: Dev/trunk/src/client/dojox/grid/TreeGrid.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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