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