source: Dev/branches/rest-dojo-ui/client/dojox/grid/TreeGrid.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 27.1 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        //
542        // description:
543        //              TreeGrid currently only works on "simple" structures.  That is,
544        //              single-view structures with a single row in them.
545        //
546        //              The TreeGrid works using the concept of "levels" - level 0 are the
547        //              top-level items.
548       
549        // defaultOpen: Boolean
550        //              Whether or not we default to open (all levels).  This defaults to
551        //              false for grids with a treeModel.
552        defaultOpen: true,
553
554        // sortChildItems: Boolean
555        //              If true, child items will be returned sorted according to the sorting
556        //              properties of the grid.
557        sortChildItems: false,
558
559        // openAtLevels: Array
560        //              Which levels we are open at (overrides defaultOpen for the values
561        //              that exist here).  Its values can be a boolean (true/false) or an
562        //              integer (for the # of children to be closed if there are more than
563        //              that)
564        openAtLevels: [],
565       
566        // treeModel: dijit.tree.ForestStoreModel
567        //              A dijit.Tree model that will be used instead of using aggregates.
568        //              Setting this value will make the TreeGrid behave like a columnar
569        //              tree.  When setting this value, defaultOpen will default to false,
570        //              and openAtLevels will be ignored.
571        treeModel: null,
572       
573        // expandoCell: Integer
574        //              When used in conjunction with a treeModel (see above), this is a 0-based
575        //              index of the cell in which to place the actual expando
576        expandoCell: 0,
577       
578        // private values
579        // aggregator: Object
580        //              The aggregator class - it will be populated automatically if we
581        //              are a collapsable grid
582        aggregator: null,
583
584
585        // Override this to get our "magic" layout
586        _layoutClass: _TreeLayout,
587
588        createSelection: function(){
589                this.selection = new TreeSelection(this);
590        },
591
592        _childItemSorter: function(a, b, attribute, descending){
593                var av = this.store.getValue(a, attribute);
594                var bv = this.store.getValue(b, attribute);
595                if(av != bv){
596                        return av < bv == descending ? 1 : -1;
597                }
598                return 0;
599        },
600
601        _onNew: function(item, parentInfo){
602                if(!parentInfo || !parentInfo.item){
603                        this.inherited(arguments);
604                }else{
605                        var idx = this.getItemIndex(parentInfo.item);
606                        if(typeof idx == "string"){
607                                this.updateRow(idx.split('/')[0]);
608                        }else if(idx > -1){
609                                this.updateRow(idx);
610                        }
611                }
612        },
613
614        _onSet: function(item, attribute, oldValue, newValue){
615                this._checkUpdateStatus();
616                if(this.aggregator){
617                        this.aggregator.clearSubtotalCache();
618                }
619                var idx = this.getItemIndex(item);
620                if(typeof idx == "string"){
621                        this.updateRow(idx.split('/')[0]);
622                }else if(idx > -1){
623                        this.updateRow(idx);
624                }
625        },
626
627        _onDelete: function(item){
628                this._cleanupExpandoCache(this._getItemIndex(item, true), this.store.getIdentity(item), item);
629                this.inherited(arguments);
630        },
631
632        _cleanupExpandoCache: function(index, identity, item){},
633
634        _addItem: function(item, index, noUpdate, dontUpdateRoot){
635                // add our root items to the root of the model's children
636                // list since we don't query the model
637                if(!dontUpdateRoot && this.model && array.indexOf(this.model.root.children, item) == -1){
638                        this.model.root.children[index] = item;
639                }
640                this.inherited(arguments);
641        },
642
643        getItem: function(/*integer|Array|String*/ idx){
644                // summary:
645                //              overridden so that you can pass in a '/' delimited string of indexes to get the
646                //              item based off its path...that is, passing in "1/3/2" will get the
647                //              3rd (0-based) child from the 4th child of the 2nd top-level item.
648                var isArray = lang.isArray(idx);
649                if(lang.isString(idx) && idx.indexOf('/')){
650                        idx = idx.split('/');
651                        isArray = true;
652                }
653                if(isArray && idx.length == 1){
654                        idx = idx[0];
655                        isArray = false;
656                }
657                if(!isArray){
658                        return DataGrid.prototype.getItem.call(this, idx);
659                }
660                var s = this.store;
661                var itm = DataGrid.prototype.getItem.call(this, idx[0]);
662                var cf, i, j;
663                if(this.aggregator){
664                        cf = this.aggregator.childFields||[];
665                        if(cf){
666                                for(i = 0; i < idx.length - 1 && itm; i++){
667                                        if(cf[i]){
668                                                itm = (s.getValues(itm, cf[i])||[])[idx[i + 1]];
669                                        }else{
670                                                itm = null;
671                                        }
672                                }
673                        }
674                }else if(this.treeModel){
675                        cf = this.treeModel.childrenAttrs||[];
676                        if(cf&&itm){
677                                for(i=1, il=idx.length; (i<il) && itm; i++) {
678                                        for(j=0, jl=cf.length; j<jl; j++) {
679                                                if(cf[j]){
680                                                        itm = (s.getValues(itm, cf[j])||[])[idx[i]];
681                                                }else{
682                                                        itm = null;
683                                                }
684                                                if(itm){ break; }
685                                        }
686                                }
687                        }
688                }
689                return itm || null;
690        },
691
692        _getItemIndex: function(item, isDeleted){
693                if(!isDeleted && !this.store.isItem(item)){
694                        return -1;
695                }
696                var idx = this.inherited(arguments);
697                if(idx == -1){
698                        var idty = this.store.getIdentity(item);
699                        return this._by_idty_paths[idty] || -1;
700                }
701                return idx;
702        },
703       
704        postMixInProperties: function(){
705                if(this.treeModel && !("defaultOpen" in this.params)){
706                        // Default open to false for tree models, true for other tree
707                        // grids.
708                        this.defaultOpen = false;
709                }
710                var def = this.defaultOpen;
711                this.openAtLevels = array.map(this.openAtLevels, function(l){
712                        if(typeof l == "string"){
713                                switch(l.toLowerCase()){
714                                        case "true":
715                                                return true;
716                                                break;
717                                        case "false":
718                                                return false;
719                                                break;
720                                        default:
721                                                var r = parseInt(l, 10);
722                                                if(isNaN(r)){
723                                                        return def;
724                                                }
725                                                return r;
726                                                break;
727                                }
728                        }
729                        return l;
730                });
731                this._by_idty_paths = {};
732                this.inherited(arguments);
733        },
734       
735    postCreate: function(){
736        this.inherited(arguments);
737                if(this.treeModel){
738                        this._setModel(this.treeModel);
739                }
740    },
741
742        setModel: function(treeModel){
743                this._setModel(treeModel);
744                this._refresh(true);
745        },
746       
747        _setModel: function(treeModel){
748                if(treeModel && (!ForestStoreModel || !(treeModel instanceof ForestStoreModel))){
749                        throw new Error("dojox.grid.TreeGrid: treeModel must be an instance of dijit.tree.ForestStoreModel");
750                }
751                this.treeModel = treeModel;
752                domClass.toggle(this.domNode, "dojoxGridTreeModel", this.treeModel ? true : false);
753                this._setQuery(treeModel ? treeModel.query : null);
754                this._setStore(treeModel ? treeModel.store : null);
755        },
756
757        createScroller: function(){
758                this.inherited(arguments);
759                this.scroller._origDefaultRowHeight = this.scroller.defaultRowHeight;
760        },
761       
762        createManagers: function(){
763                // summary:
764                //              create grid managers for various tasks including rows, focus, selection, editing
765
766                // row manager
767                this.rows = new _RowManager(this);
768                // focus manager
769                this.focus = new _TreeFocusManager(this);
770                // edit manager
771                this.edit = new _EditManager(this);
772        },
773
774        _setStore: function(store){
775                this.inherited(arguments);
776                if(this.treeModel&&!this.treeModel.root.children){
777                        this.treeModel.root.children = [];
778                }
779                if(this.aggregator){
780                        this.aggregator.store = store;
781                }
782        },
783       
784        getDefaultOpenState: function(cellDef, item){
785                // summary:
786                //              Returns the default open state for the given definition and item
787                //              It reads from the openAtLevels and defaultOpen values of the
788                //              grid to calculate if the given item should default to open or
789                //              not.
790                var cf;
791                var store = this.store;
792                if(this.treeModel){ return this.defaultOpen; }
793                if(!cellDef || !store || !store.isItem(item) ||
794                                !(cf = this.aggregator.childFields[cellDef.level])){
795                        return this.defaultOpen;
796                }
797                if(this.openAtLevels.length > cellDef.level){
798                        var dVal = this.openAtLevels[cellDef.level];
799                        if(typeof dVal == "boolean"){
800                                return dVal;
801                        }else if(typeof dVal == "number"){
802                                return (store.getValues(item, cf).length <= dVal);
803                        }
804                }
805                return this.defaultOpen;
806        },
807        onStyleRow: function(row){
808                if(!this.layout._isCollapsable){
809                        this.inherited(arguments);
810                        return;
811                }
812                var base = domAttr.get(row.node, 'dojoxTreeGridBaseClasses');
813                if(base){
814                        row.customClasses = base;
815                }
816                var i = row;
817                var tagName = i.node.tagName.toLowerCase();
818                i.customClasses += (i.odd?" dojoxGridRowOdd":"") +
819                                                   (i.selected&&tagName=='tr'?" dojoxGridRowSelected":"") +
820                                                   (i.over&&tagName=='tr'?" dojoxGridRowOver":"");
821                this.focus.styleRow(i);
822                this.edit.styleRow(i);
823        },
824        styleRowNode: function(inRowIndex, inRowNode){
825                if(inRowNode){
826                        if(inRowNode.tagName.toLowerCase() == 'div' && this.aggregator){
827                                query("tr[dojoxTreeGridPath]", inRowNode).forEach(function(rowNode){
828                                        this.rows.styleRowNode(domAttr.get(rowNode, 'dojoxTreeGridPath'), rowNode);
829                                },this);
830                        }
831                        this.rows.styleRowNode(inRowIndex, inRowNode);
832                }
833        },
834        onCanSelect: function(inRowIndex){
835                var nodes = query("tr[dojoxTreeGridPath='" + inRowIndex + "']", this.domNode);
836                if(nodes.length){
837                        if(domClass.contains(nodes[0], 'dojoxGridSummaryRow')){
838                                return false;
839                        }
840                }
841                return this.inherited(arguments);
842        },
843        onKeyDown: function(e){
844                if(e.altKey || e.metaKey){
845                        return;
846                }
847                switch(e.keyCode){
848                        case keys.UP_ARROW:
849                                if(!this.edit.isEditing() && this.focus.rowIndex != "0"){
850                                        event.stop(e);
851                                        this.focus.move(-1, 0);
852                                }
853                                break;
854                        case keys.DOWN_ARROW:
855                                var currPath = new TreePath(this.focus.rowIndex, this);
856                                var lastPath = new TreePath(this.rowCount-1, this);
857                                lastPath = lastPath.lastChild(true);
858                                if(!this.edit.isEditing() && currPath.toString() != lastPath.toString()){
859                                        event.stop(e);
860                                        this.focus.move(1, 0);
861                                }
862                                break;
863                        default:
864                                this.inherited(arguments);
865                                break;
866                }
867        },
868        canEdit: function(inCell, inRowIndex){
869                var node = inCell.getNode(inRowIndex);
870                return node && this._canEdit;
871        },
872        doApplyCellEdit: function(inValue, inRowIndex, inAttrName){
873                var item = this.getItem(inRowIndex);
874                var oldValue = this.store.getValue(item, inAttrName);
875                if(typeof oldValue == 'number'){
876                        inValue = isNaN(inValue) ? inValue : parseFloat(inValue);
877                }else if(typeof oldValue == 'boolean'){
878                        inValue = inValue == 'true' ? true : inValue == 'false' ? false : inValue;
879                }else if(oldValue instanceof Date){
880                        var asDate = new Date(inValue);
881                        inValue = isNaN(asDate.getTime()) ? inValue : asDate;
882                }
883                this.store.setValue(item, inAttrName, inValue);
884                this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
885        }
886});
887TreeGrid.markupFactory = function(props, node, ctor, cellFunc){
888        var widthFromAttr = function(n){
889                var w = domAttr.get(n, "width")||"auto";
890                if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){
891                        w = parseInt(w, 10)+"px";
892                }
893                return w;
894        };
895       
896        var cellsFromMarkup = function(table){
897                var rows;
898                // Don't support colgroup on our grid - single view, single row only
899                if(table.nodeName.toLowerCase() == "table" &&
900                                        query("> colgroup", table).length === 0 &&
901                                        (rows = query("> thead > tr", table)).length == 1){
902                        var tr = rows[0];
903                        return query("> th", rows[0]).map(function(th){
904                                // Grab type and field (the only ones that are shared
905                                var cell = {
906                                        type: lang.trim(domAttr.get(th, "cellType")||""),
907                                        field: lang.trim(domAttr.get(th, "field")||"")
908                                };
909                                if(cell.type){
910                                        cell.type = lang.getObject(cell.type);
911                                }
912                               
913                                var subTable = query("> table", th)[0];
914                                if(subTable){
915                                        // If we have a subtable, we are an aggregate and a summary cell
916                                        cell.name = "";
917                                        cell.children = cellsFromMarkup(subTable);
918                                        if(domAttr.has(th, "itemAggregates")){
919                                                cell.itemAggregates = array.map(domAttr.get(th, "itemAggregates").split(","), function(v){
920                                                        return lang.trim(v);
921                                                });
922                                        }else{
923                                                cell.itemAggregates = [];
924                                        }
925                                        if(domAttr.has(th, "aggregate")){
926                                                cell.aggregate = domAttr.get(th, "aggregate");
927                                        }
928                                        cell.type = cell.type || dojox.grid.cells.SubtableCell;
929                                }else{
930                                        // Grab our other stuff we need (mostly what's in the normal
931                                        // Grid)
932                                        cell.name = lang.trim(domAttr.get(th, "name")||th.innerHTML);
933                                        if(domAttr.has(th, "width")){
934                                                cell.width = widthFromAttr(th);
935                                        }
936                                        if(domAttr.has(th, "relWidth")){
937                                                cell.relWidth = window.parseInt(domAttr.get(th, "relWidth"), 10);
938                                        }
939                                        if(domAttr.has(th, "hidden")){
940                                                cell.hidden = domAttr.get(th, "hidden") == "true";
941                                        }
942                                        cell.field = cell.field||cell.name;
943                                        DataGrid.cell_markupFactory(cellFunc, th, cell);
944                                        cell.type = cell.type || dojox.grid.cells.Cell;
945                                }
946                                if(cell.type && cell.type.markupFactory){
947                                        cell.type.markupFactory(th, cell);
948                                }
949                                return cell;
950                        });
951                }
952                return [];
953        };
954       
955        var rows;
956        if(     !props.structure ){
957                var row = cellsFromMarkup(node);
958                if(row.length){
959                        // Set our structure here - so that we don't try and set it in the
960                        // markup factory
961                        props.structure = [{__span: Infinity, cells:[row]}];
962                }
963        }
964        return DataGrid.markupFactory(props, node, ctor, cellFunc);
965};
966
967return TreeGrid;
968
969});
Note: See TracBrowser for help on using the repository browser.