source: Dev/trunk/src/client/dojox/grid/LazyTreeGrid.js @ 532

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

Added Dojo 1.9.3 release.

File size: 25.5 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                        var dir = this.textDir || this.grid.textDir;
263                        var ret = this._defaultFormat(d, [d, rowIndex, level, this]);
264                        if(dir && this._enforceTextDirWithUcc){
265                            ret = this._enforceTextDirWithUcc(dir, ret);
266                        }
267                        return ret;
268                }
269        }
270});
271
272var _LazyTreeLayout = declare("dojox.grid._LazyTreeLayout", _Layout, {
273        // summary:
274        //              Override the dojox.grid._TreeLayout to modify the _TreeGridView and cell formatter
275        setStructure: function(structure){
276                var g = this.grid, s = structure;
277                if(g && !array.every(s, function(i){
278                        return !!i.cells;
279                })){
280                        s = arguments[0] = [{cells:[s]}];//intentionally change arguments[0]
281                }
282                if(s.length === 1 && s[0].cells.length === 1){
283                        s[0].type = "dojox.grid._TreeGridView";
284                        this._isCollapsable = true;
285                        s[0].cells[0][this.grid.expandoCell].isCollapsable = true;
286                }
287                this.inherited(arguments);
288        },
289        addCellDef: function(rowIndex, cellIndex, def){
290                var obj = this.inherited(arguments);
291                return lang.mixin(obj, LazyTreeCell);
292        }
293});
294
295var _LazyTreeGridCache = declare("dojox.grid._LazyTreeGridCache", null, {
296        // summary:
297        //              An internal object used to cache the tree path and open state of each item.
298        //              The form of the cache items would be an object array:
299        //              [{opened: true/false, treePath: [level0 parent id, level1 parent id, ...]}]
300        // example:
301        //              | [{opened: true, treePath: []},
302        //              |  {opened: false, treePath: ["root0"]},
303        //              |  {opened: false, treePath: ["root0"]},
304        //              |  {opened: false, treePath: []},
305        //              |  ...]
306        constructor: function(){
307                this.items = [];
308        },
309        getSiblingIndex: function(rowIndex, treePath){
310                var i = rowIndex - 1, indexCount = 0, tp;
311                for(; i >=0; i--){
312                        tp = this.items[i] ? this.items[i].treePath : [];
313                        if(tp.join('/') === treePath.join('/')){
314                                indexCount++;
315                        }else if(tp.length < treePath.length){
316                                break;
317                        }
318                }
319                return indexCount;
320        },
321        removeChildren: function(rowIndex){
322                // find next sibling index
323                var i = rowIndex + 1, count, tp,
324                        treePath = this.items[rowIndex] ? this.items[rowIndex].treePath : [];
325                for(; i < this.items.length; i++){
326                        tp = this.items[i] ? this.items[i].treePath : [];
327                        if(tp.join('/') === treePath.join('/') || tp.length <= treePath.length){
328                                break;
329                        }
330                }
331                count = i - (rowIndex + 1);
332                this.items.splice(rowIndex + 1, count);
333                return count;
334        }
335});
336
337var LazyTreeGrid = declare("dojox.grid.LazyTreeGrid", TreeGrid, {
338        // summary:
339        //              An enhanced TreeGrid widget which supports lazy-loading for nested children items
340        //
341        // description:
342        //              LazyTreeGrid inherits from dojo.grid.TreeGrid and applies virtual scrolling mechanism
343        //              to nested children rows so that it's possible to deal with complex tree structure data set
344        //              with nested and huge children rows. It's also compatible with dijit.tree.ForestStoreModel
345        //
346        //              Most methods and properties pertaining to dojox.grid.DataGrid
347        //              and dojox.grid.TreeGrid also apply here
348        //
349        //              LazyTreeGrid does not support summary row/items aggregate due to the lazy-loading rationale.
350
351        _layoutClass: _LazyTreeLayout,
352        _size: 0,
353
354        // treeModel: dijit/tree/.ForestStoreModel|dojox/grid/LazyTreeGridStoreModel
355        //              A tree store model object.
356        treeModel: null,
357
358        // defaultState: Object
359        //              Used to restore the state of LazyTreeGrid.
360        //              This object should ONLY be obtained from `LazyTreeGrid.getState()`.
361        defaultState: null,
362
363        // colSpans: Object
364        //              A json object that defines column span of each level rows.  Attributes:
365        //
366        //              - 0/1/..: which level need to colspan
367        //              - start: start column index of colspan
368        //              - end: end column index of colspan
369        //              - primary: index of column which content will be displayed (default is value of start).
370        //
371        //              example:
372        //              |       colSpans = {
373        //              |       0:      [
374        //              |                       {start: 0, end: 1, primary: 0},
375        //              |                       {start: 2, end: 4, primary: 3}
376        //              |               ],
377        //              |       1:      [
378        //              |                       {start: 0, end: 3, primary: 1}
379        //              |               ]
380        //              |       };
381        colSpans: null,
382       
383        postCreate: function(){
384                this._setState();
385                this.inherited(arguments);
386                if(!this._treeCache){
387                        this._treeCache = new _LazyTreeGridCache();
388                }
389                if(!this.treeModel || !(this.treeModel instanceof dijit.tree.ForestStoreModel)){
390                        throw new Error("dojox.grid.LazyTreeGrid: must be used with a treeModel which is an instance of dijit.tree.ForestStoreModel");
391                }
392                domClass.add(this.domNode, "dojoxGridTreeModel");
393                dom.setSelectable(this.domNode, this.selectable);
394        },
395        createManagers: function(){
396                this.rows = new _RowManager(this);
397                this.focus = new _FocusManager(this);
398                this.edit = new _EditManager(this);
399        },
400        createSelection: function(){
401                this.selection = new DataSelection(this);
402        },
403        setModel: function(treeModel){
404                if(!treeModel){
405                        return;
406                }
407                this._setModel(treeModel);
408                this._cleanup();
409                this._refresh(true);
410        },
411        setStore: function(store, query, queryOptions){
412                if(!store){
413                        return;
414                }
415                this._setQuery(query, queryOptions);
416                this.treeModel.query = query;
417                this.treeModel.store = store;
418                this.treeModel.root.children = [];
419                this.setModel(this.treeModel);
420        },
421        onSetState: function(){
422                // summary:
423                //              Event fired when a default state being set.
424        },
425        _setState: function(){
426                if(this.defaultState){
427                        this._treeCache = this.defaultState.cache;
428                        this.sortInfo = this.defaultState.sortInfo || 0;
429                        this.query = this.defaultState.query || this.query;
430                        this._lastScrollTop = this.defaultState.scrollTop;
431                        if(this.keepSelection){
432                                this.selection.preserver._selectedById = this.defaultState.selection;
433                        }else{
434                                this.selection.selected = this.defaultState.selection || [];
435                        }
436                        this.onSetState();
437                }
438        },
439        getState: function(){
440                // summary:
441                //              Get the current state of LazyTreeGrid including expanding, sorting, selection and scroll top state.
442                var _this = this,
443                        selection = this.keepSelection ? this.selection.preserver._selectedById : this.selection.selected;
444                return {
445                        cache: lang.clone(_this._treeCache),
446                        query: lang.clone(_this.query),
447                        sortInfo: lang.clone(_this.sortInfo),
448                        scrollTop: lang.clone(_this.scrollTop),
449                        selection: lang.clone(selection)
450                };
451        },
452        _setQuery: function(query, queryOptions){
453                this.inherited(arguments);
454                this.treeModel.query = query;
455        },
456        filter: function(query, reRender){
457                this._cleanup();
458                this.inherited(arguments);
459    },
460        destroy: function(){
461                this._cleanup();
462                this.inherited(arguments);
463        },
464        expand: function(itemId){
465                // summary:
466                //              Expand the row with the given itemId.
467                // itemId: String?
468                this._fold(itemId, true);
469        },
470        collapse: function(itemId){
471                // summary:
472                //              Collapse the row with the given itemId.
473                // itemId: String?
474                this._fold(itemId, false);
475        },
476        refresh: function(keepState){
477                // summary:
478                //              Refresh, and persist the expand/collapse state when keepState equals true
479                // keepState: Boolean
480                if(!keepState){
481                        this._cleanup();
482                }
483                this._refresh(true);
484        },
485        _cleanup: function(){
486                this._treeCache.items = [];
487                this._onCleanupExpandoCache();
488        },
489        setSortIndex: function(inIndex, inAsc){
490                // Need to clean up the cache before sorting
491                if(this.canSort(inIndex + 1)){
492                        this._cleanup();
493                }
494                this.inherited(arguments);
495        },
496        _refresh: function(isRender){
497                this._clearData();
498                this.updateRowCount(this._size);
499                this._fetch(0, true);
500        },
501        render: function(){
502                this.inherited(arguments);
503                this.setScrollTop(this.scrollTop);
504        },
505        _onNew: function(item, parentInfo){
506                var addingChild = parentInfo && this.store.isItem(parentInfo.item) && array.some(this.treeModel.childrenAttrs, function(c){
507                        return c === parentInfo.attribute;
508                });
509                var items = this._treeCache.items, byIdx = this._by_idx;
510                if(!addingChild){
511                        items.push({opened: false, treePath: []});
512                        this._size += 1;
513                        this.inherited(arguments);
514                }else{
515                        var parentItem = parentInfo.item,
516                                parentIdty = this.store.getIdentity(parentItem),
517                                rowIndex = -1, i = 0;
518                        for(; i < byIdx.length; i++){
519                                if(parentIdty === byIdx[i].idty){
520                                        rowIndex = i;
521                                        break;
522                                }
523                        }
524                        if(rowIndex >= 0){
525                                if(items[rowIndex] && items[rowIndex].opened){
526                                        var parentTreePath = items[rowIndex].treePath, pos = rowIndex + 1;
527                                        for(; pos < items.length; pos++){
528                                                if(items[pos].treePath.length <= parentTreePath.length){
529                                                        break;
530                                                }
531                                        }
532                                        var treePath = parentTreePath.slice();
533                                        treePath.push(parentIdty);
534                                        this._treeCache.items.splice(pos, 0, {opened: false, treePath: treePath});
535                                        // update grid._by_idx
536                                        var idty = this.store.getIdentity(item);
537                                        this._by_idty[idty] = { idty: idty, item: item };
538                                        byIdx.splice(pos, 0, this._by_idty[idty]);
539                                        // update grid
540                                        this._size += 1;
541                                        this.updateRowCount(this._size);
542                                        this._updateRenderedRows(pos);
543                                }else{
544                                        this.updateRow(rowIndex);
545                                }
546                        }
547                }
548        },
549        _onDelete: function(item){
550                var i = 0, rowIndex = -1, idty = this.store.getIdentity(item);
551                for(; i < this._by_idx.length; i++){
552                        if(idty === this._by_idx[i].idty){
553                                rowIndex = i;
554                                break;
555                        }
556                }
557                if(rowIndex >= 0){
558                        var items = this._treeCache.items, treePath = items[rowIndex] ? items[rowIndex].treePath : [], tp, count = 1;
559                        i = rowIndex + 1;
560                        for(; i < this._size; i++, count++){
561                                tp = items[i] ? items[i].treePath : [];
562                                if(items[i].treePath.length <= treePath.length){
563                                        break;
564                                }
565                        }
566                        items.splice(rowIndex, count);
567                        this._onCleanupExpandoCache(idty);
568                        this._by_idx.splice(rowIndex, count);
569                        this._size -= count;
570                        this.updateRowCount(this._size);
571                        this._updateRenderedRows(rowIndex);
572                }
573        },
574        _onCleanupExpandoCache: function(identity){},
575        _fetch: function(start, isRender){
576                if(!this._loading){
577                        this._loading = true;
578                }
579                start = start || 0;
580                var count = this._size - start > 0 ? Math.min(this.rowsPerPage, this._size - start) : this.rowsPerPage;
581                var i = 0;
582                var fetchedItems = [];
583                this._reqQueueLen = 0;
584                for(; i < count; i++){
585                        if(this._by_idx[start + i]){
586                                fetchedItems.push(this._by_idx[start + i].item);
587                        }else{
588                                break;
589                        }
590                }
591                if(fetchedItems.length === count){
592                        this._reqQueueLen = 1;
593                        this._onFetchBegin(this._size, {startRowIdx: start, count: count});
594                        this._onFetchComplete(fetchedItems, {startRowIdx: start, count: count});
595                }else{
596                        var level, nextLevel, len = 1, items = this._treeCache.items,
597                                treePath = items[start] ? items[start].treePath : [];
598                        for(i = 1; i < count; i++){
599                                level = items[start + len - 1] ? items[start + len - 1].treePath.length : 0;
600                                nextLevel = items[start + len] ? items[start + len].treePath.length : 0;
601                                if(level !== nextLevel){
602                                        this._reqQueueLen++;
603                                        this._fetchItems({startRowIdx: start, count: len, treePath: treePath});
604                                        start = start + len;
605                                        len = 1;
606                                        treePath = items[start] ? items[start].treePath : 0;
607                                }else{
608                                        len++;
609                                }
610                        }
611                        this._reqQueueLen++;
612                        this._fetchItems({startRowIdx: start, count: len, treePath: treePath});
613                }
614        },
615        _fetchItems: function(req){
616                if(this._pending_requests[req.startRowIdx]){
617                        return;
618                }
619                this.showMessage(this.loadingMessage);
620                this._pending_requests[req.startRowIdx] = true;
621                var     onError = lang.hitch(this, '_onFetchError'),
622                        start = this._treeCache.getSiblingIndex(req.startRowIdx, req.treePath);
623                if(req.treePath.length === 0){
624                        this.store.fetch({
625                                start: start,
626                                startRowIdx: req.startRowIdx,
627                                treePath: req.treePath,
628                                count: req.count,
629                                query: this.query,
630                                sort: this.getSortProps(),
631                                queryOptions: this.queryOptions,
632                                onBegin: lang.hitch(this, '_onFetchBegin'),
633                                onComplete: lang.hitch(this, '_onFetchComplete'),
634                                onError: lang.hitch(this, '_onFetchError')
635                        });
636                }else{
637                        var parentId = req.treePath[req.treePath.length - 1], parentItem;
638                        var queryObj = {
639                                start: start,
640                                startRowIdx: req.startRowIdx,
641                                treePath: req.treePath,
642                                count: req.count,
643                                parentId: parentId,
644                                sort: this.getSortProps()
645                        };
646                        var _this = this;
647                        var onComplete = function(){
648                                var f = lang.hitch(_this, '_onFetchComplete');
649                                if(arguments.length == 1){
650                                        f.apply(_this, [arguments[0], queryObj]);
651                                }else{
652                                        f.apply(_this, arguments);
653                                }
654                        };
655                        if(this._by_idty[parentId]){
656                                parentItem = this._by_idty[parentId].item;
657                                this.treeModel.getChildren(parentItem, onComplete, onError, queryObj);
658                        }else{
659                                this.store.fetchItemByIdentity({
660                                        identity: parentId,
661                                        onItem: function(item){
662                                                _this.treeModel.getChildren(item, onComplete, onError, queryObj);
663                                        },
664                                        onError: onError
665                                });
666                        }
667                }
668        },
669        _onFetchBegin: function(size, request){
670                if(this._treeCache.items.length === 0){
671                        this._size = parseInt(size, 10);
672                }
673                size = this._size;
674                // this._size = size = this._treeCache.items.length;
675                this.inherited(arguments);
676        },
677        _onFetchComplete: function(items, request){
678                var startRowIdx = request.startRowIdx,
679                        count = request.count,
680                        start = items.length <= count ? 0: request.start,
681                        treePath = request.treePath || [];
682                if(lang.isArray(items) && items.length > 0){
683                        var i = 0, len = Math.min(count, items.length);
684                        for(; i < len; i++){
685                                if(!this._treeCache.items[startRowIdx + i]){
686                                        this._treeCache.items[startRowIdx + i] = {opened: false, treePath: treePath};
687                                }
688                                if(!this._by_idx[startRowIdx + i]){
689                                        this._addItem(items[start + i], startRowIdx + i, true);
690                                }
691                                // this._treeCache.items.splice(startRowIdx + i, 0, {opened: false, treePath: treePath});
692                        }
693                        this.updateRows(startRowIdx, len);
694                }
695                if(this._size == 0){
696                        this.showMessage(this.noDataMessage);
697                }else{
698                        this.showMessage();
699                }
700                this._pending_requests[startRowIdx] = false;
701                this._reqQueueLen--;
702                if(this._loading && this._reqQueueLen === 0){
703                        this._loading = false;
704                        if(this._lastScrollTop){
705                                this.setScrollTop(this._lastScrollTop);
706                        }
707                }
708        },
709        expandoFetch: function(rowIndex, open){
710                // summary:
711                //              Function for fetch children of a given row
712                if(this._loading || !this._by_idx[rowIndex]){return;}
713                this._loading = true;
714                this._toggleLoadingClass(rowIndex, true);
715                this.expandoRowIndex = rowIndex;
716                var item = this._by_idx[rowIndex].item;
717                // this._pages = [];
718                if(open){
719                        var queryObj = {
720                                start: 0,
721                                count: this.rowsPerPage,
722                                parentId: this.store.getIdentity(this._by_idx[rowIndex].item),
723                                sort: this.getSortProps()
724                        };
725                        this.treeModel.getChildren(item, lang.hitch(this, "_onExpandoComplete"), lang.hitch(this, "_onFetchError"), queryObj);
726                }else{
727                        // get the whole children number when clear the children from cache
728                        var num = this._treeCache.removeChildren(rowIndex);
729                        // remove the items from grid._by_idx
730                        this._by_idx.splice(rowIndex + 1, num);
731                        this._bop = this._eop = -1;
732                        //update grid
733                        this._size -= num;
734                        this.updateRowCount(this._size);
735                        this._updateRenderedRows(rowIndex + 1);
736                        this._toggleLoadingClass(rowIndex, false);
737                        if(this._loading){
738                                this._loading = false;
739                        }
740                        this.focus._delayedCellFocus();
741                }
742        },
743        _onExpandoComplete: function(childItems, request, size){
744                size = isNaN(size) ? childItems.length : parseInt(size, 10);
745                var treePath = this._treeCache.items[this.expandoRowIndex].treePath.slice(0);
746                treePath.push(this.store.getIdentity(this._by_idx[this.expandoRowIndex].item));
747                var i = 1, idty;
748                for(; i <= size; i++){
749                        this._treeCache.items.splice(this.expandoRowIndex + i, 0, {treePath: treePath, opened: false});
750                }
751                this._size += size;
752                this.updateRowCount(this._size);
753                for(i = 0; i < size; i++){
754                        if(childItems[i]){
755                                idty = this.store.getIdentity(childItems[i]);
756                                this._by_idty[idty] = { idty: idty, item: childItems[i] };
757                                this._by_idx.splice(this.expandoRowIndex + 1 + i, 0, this._by_idty[idty]);
758                        }else{
759                                this._by_idx.splice(this.expandoRowIndex + 1 + i, 0, null);
760                        }
761                }
762                this._updateRenderedRows(this.expandoRowIndex + 1);
763                this._toggleLoadingClass(this.expandoRowIndex, false);
764                this.stateChangeNode = null;
765                if(this._loading){
766                        this._loading = false;
767                }
768                if(this.autoHeight === true){
769                        this._resize();
770                }
771                this.focus._delayedCellFocus();
772        },
773        styleRowNode: function(rowIndex, rowNode){
774                if(rowNode){
775                        this.rows.styleRowNode(rowIndex, rowNode);
776                }
777        },
778        onStyleRow: function(row){
779                if(!this.layout._isCollapsable){
780                        this.inherited(arguments);
781                        return;
782                }
783                row.customClasses += (row.odd ? " dojoxGridRowOdd" : "") + (row.selected ? " dojoxGridRowSelected" : "") + (row.over ? " dojoxGridRowOver" : "");
784                this.focus.styleRow(row);
785                this.edit.styleRow(row);
786        },
787        onKeyDown: function(e){
788                if(e.altKey || e.metaKey){
789                        return;
790                }
791                var expando = dijit.findWidgets(e.target)[0];
792                if(e.keyCode === keys.ENTER && expando instanceof _LazyExpando){
793                        expando.onToggle();
794                }
795                this.inherited(arguments);
796        },
797        _toggleLoadingClass: function(rowIndex, flag){
798                var views = this.views.views, node,
799                        rowNode = views[views.length - 1].getRowNode(rowIndex);
800                if(rowNode){
801                        node = query('.dojoxGridExpando', rowNode)[0];
802                        if(node){
803                                domClass.toggle(node, "dojoxGridExpandoLoading", flag);
804                        }
805                }
806        },
807        _updateRenderedRows: function(start){
808                array.forEach(this.scroller.stack, function(p){
809                        if(p * this.rowsPerPage >= start){
810                                this.updateRows(p * this.rowsPerPage, this.rowsPerPage);
811                        }else if((p + 1) * this.rowsPerPage >=  start){
812                                this.updateRows(start, (p + 1) * this.rowsPerPage - start + 1);
813                        }
814                }, this);
815        },
816        _fold: function(itemId, open){
817                var rowIndex = -1, i = 0, byIdx = this._by_idx, idty = this._by_idty[itemId];
818                if(idty && idty.item && this.treeModel.mayHaveChildren(idty.item)){
819                        for(; i < byIdx.length; i++){
820                                if(byIdx[i] && byIdx[i].idty === itemId){
821                                        rowIndex = i;
822                                        break;
823                                }
824                        }
825                        if(rowIndex >= 0){
826                                var rowNode = this.views.views[this.views.views.length - 1].getRowNode(rowIndex);
827                                if(rowNode){
828                                        var expando = dijit.findWidgets(rowNode)[0];
829                                        if(expando){
830                                                expando.setOpen(open);
831                                        }
832                                }
833                        }
834                }
835        }
836});
837
838LazyTreeGrid.markupFactory = function(props, node, ctor, cellFunc){
839        return TreeGrid.markupFactory(props, node, ctor, cellFunc);
840};
841
842return LazyTreeGrid;
843
844});
Note: See TracBrowser for help on using the repository browser.