source: Dev/trunk/src/client/dojox/grid/_Scroller.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: 14.7 KB
Line 
1define([
2        "dijit/registry",
3        "dojo/_base/declare",
4        "dojo/_base/lang",
5        "./util",
6        "dojo/_base/html"
7], function(dijitRegistry, declare, lang, util, html){
8
9        var indexInParent = function(inNode){
10                var i=0, n, p=inNode.parentNode;
11                while((n = p.childNodes[i++])){
12                        if(n == inNode){
13                                return i - 1;
14                        }
15                }
16                return -1;
17        };
18       
19        var cleanNode = function(inNode){
20                if(!inNode){
21                        return;
22                }
23                dojo.forEach(dijitRegistry.toArray(), function(w){
24                        if(w.domNode && html.isDescendant(w.domNode, inNode, true)){
25                                w.destroy();
26                        }
27                });
28        };
29
30        var getTagName = function(inNodeOrId){
31                var node = html.byId(inNodeOrId);
32                return (node && node.tagName ? node.tagName.toLowerCase() : '');
33        };
34       
35        var nodeKids = function(inNode, inTag){
36                var result = [];
37                var i=0, n;
38                while((n = inNode.childNodes[i])){
39                        i++;
40                        if(getTagName(n) == inTag){
41                                result.push(n);
42                        }
43                }
44                return result;
45        };
46       
47        var divkids = function(inNode){
48                return nodeKids(inNode, 'div');
49        };
50
51        return declare("dojox.grid._Scroller", null, {
52                constructor: function(inContentNodes){
53                        this.setContentNodes(inContentNodes);
54                        this.pageHeights = [];
55                        this.pageNodes = [];
56                        this.stack = [];
57                },
58                // specified
59                rowCount: 0, // total number of rows to manage
60                defaultRowHeight: 32, // default height of a row
61                keepRows: 100, // maximum number of rows that should exist at one time
62                contentNode: null, // node to contain pages
63                scrollboxNode: null, // node that controls scrolling
64                // calculated
65                defaultPageHeight: 0, // default height of a page
66                keepPages: 10, // maximum number of pages that should exists at one time
67                pageCount: 0,
68                windowHeight: 0,
69                firstVisibleRow: 0,
70                lastVisibleRow: 0,
71                averageRowHeight: 0, // the average height of a row
72                // private
73                page: 0,
74                pageTop: 0,
75                // init
76                init: function(inRowCount, inKeepRows, inRowsPerPage){
77                        switch(arguments.length){
78                                case 3: this.rowsPerPage = inRowsPerPage;
79                                case 2: this.keepRows = inKeepRows;
80                                case 1: this.rowCount = inRowCount;
81                                default: break;
82                        }
83                        this.defaultPageHeight = (this.grid.rowHeight > 0 ? this.grid.rowHeight : this.defaultRowHeight) * this.rowsPerPage;
84                        this.pageCount = this._getPageCount(this.rowCount, this.rowsPerPage);
85                        this.setKeepInfo(this.keepRows);
86                        this.invalidate();
87                        if(this.scrollboxNode){
88                                this.scrollboxNode.scrollTop = 0;
89                                this.scroll(0);
90                                this.scrollboxNode.onscroll = lang.hitch(this, 'onscroll');
91                        }
92                },
93                _getPageCount: function(rowCount, rowsPerPage){
94                        return rowCount ? (Math.ceil(rowCount / rowsPerPage) || 1) : 0;
95                },
96                destroy: function(){
97                        this.invalidateNodes();
98                        delete this.contentNodes;
99                        delete this.contentNode;
100                        delete this.scrollboxNode;
101                },
102                setKeepInfo: function(inKeepRows){
103                        this.keepRows = inKeepRows;
104                        this.keepPages = !this.keepRows ? this.keepPages : Math.max(Math.ceil(this.keepRows / this.rowsPerPage), 2);
105                },
106                // nodes
107                setContentNodes: function(inNodes){
108                        this.contentNodes = inNodes;
109                        this.colCount = (this.contentNodes ? this.contentNodes.length : 0);
110                        this.pageNodes = [];
111                        for(var i=0; i<this.colCount; i++){
112                                this.pageNodes[i] = [];
113                        }
114                },
115                getDefaultNodes: function(){
116                        return this.pageNodes[0] || [];
117                },
118                // updating
119                invalidate: function(){
120                        this._invalidating = true;
121                        this.invalidateNodes();
122                        this.pageHeights = [];
123                        this.height = (this.pageCount ? (this.pageCount - 1)* this.defaultPageHeight + this.calcLastPageHeight() : 0);
124                        this.resize();
125                        this._invalidating = false;
126                },
127                updateRowCount: function(inRowCount){
128                        this.invalidateNodes();
129                        this.rowCount = inRowCount;
130                        // update page count, adjust document height
131                        var oldPageCount = this.pageCount;
132                        if(oldPageCount === 0){
133                                //We want to have at least 1px in height to keep scroller.  Otherwise with an
134                                //empty grid you can't scroll to see the header.
135                                this.height = 1;
136                        }
137                        this.pageCount = this._getPageCount(this.rowCount, this.rowsPerPage);
138                        if(this.pageCount < oldPageCount){
139                                for(var i=oldPageCount-1; i>=this.pageCount; i--){
140                                        this.height -= this.getPageHeight(i);
141                                        delete this.pageHeights[i];
142                                }
143                        }else if(this.pageCount > oldPageCount){
144                                this.height += this.defaultPageHeight * (this.pageCount - oldPageCount - 1) + this.calcLastPageHeight();
145                        }
146                        this.resize();
147                },
148                // implementation for page manager
149                pageExists: function(inPageIndex){
150                        return Boolean(this.getDefaultPageNode(inPageIndex));
151                },
152                measurePage: function(inPageIndex){
153                        if(this.grid.rowHeight){
154                                return ((inPageIndex + 1) * this.rowsPerPage > this.rowCount ?
155                                        this.rowCount - inPageIndex * this.rowsPerPage :
156                                        this.rowsPerPage) * this.grid.rowHeight;
157                                         
158                        }
159                        var n = this.getDefaultPageNode(inPageIndex);
160                        return (n && n.innerHTML) ? n.offsetHeight : undefined;
161                },
162                positionPage: function(inPageIndex, inPos){
163                        for(var i=0; i<this.colCount; i++){
164                                this.pageNodes[i][inPageIndex].style.top = inPos + 'px';
165                        }
166                },
167                repositionPages: function(inPageIndex){
168                        var nodes = this.getDefaultNodes();
169                        var last = 0;
170
171                        for(var i=0; i<this.stack.length; i++){
172                                last = Math.max(this.stack[i], last);
173                        }
174                        //
175                        var n = nodes[inPageIndex];
176                        var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0);
177                        for(var p=inPageIndex+1; p<=last; p++){
178                                n = nodes[p];
179                                if(n){
180                                        if(this.getPageNodePosition(n) == y){
181                                                return;
182                                        }
183                                        this.positionPage(p, y);
184                                }
185                                y += this.getPageHeight(p);
186                        }
187                },
188                installPage: function(inPageIndex){
189                        for(var i=0; i<this.colCount; i++){
190                                this.contentNodes[i].appendChild(this.pageNodes[i][inPageIndex]);
191                        }
192                },
193                preparePage: function(inPageIndex, inReuseNode){
194                        var p = (inReuseNode ? this.popPage() : null);
195                        for(var i=0; i<this.colCount; i++){
196                                var nodes = this.pageNodes[i];
197                                var new_p = (p === null ? this.createPageNode() : this.invalidatePageNode(p, nodes));
198                                new_p.pageIndex = inPageIndex;
199                                nodes[inPageIndex] = new_p;
200                        }
201                },
202                // rendering implementation
203                renderPage: function(inPageIndex){
204                        var nodes = [];
205                        var i, j;
206                        for(i=0; i<this.colCount; i++){
207                                nodes[i] = this.pageNodes[i][inPageIndex];
208                        }
209                        for(i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
210                                this.renderRow(j, nodes);
211                        }
212                },
213                removePage: function(inPageIndex){
214                        for(var i=0, j=inPageIndex*this.rowsPerPage; i<this.rowsPerPage; i++, j++){
215                                this.removeRow(j);
216                        }
217                },
218                destroyPage: function(inPageIndex){
219                        for(var i=0; i<this.colCount; i++){
220                                var n = this.invalidatePageNode(inPageIndex, this.pageNodes[i]);
221                                if(n){
222                                        html.destroy(n);
223                                }
224                        }
225                },
226                pacify: function(inShouldPacify){
227                },
228                // pacification
229                pacifying: false,
230                pacifyTicks: 200,
231                setPacifying: function(inPacifying){
232                        if(this.pacifying != inPacifying){
233                                this.pacifying = inPacifying;
234                                this.pacify(this.pacifying);
235                        }
236                },
237                startPacify: function(){
238                        this.startPacifyTicks = new Date().getTime();
239                },
240                doPacify: function(){
241                        var result = (new Date().getTime() - this.startPacifyTicks) > this.pacifyTicks;
242                        this.setPacifying(true);
243                        this.startPacify();
244                        return result;
245                },
246                endPacify: function(){
247                        this.setPacifying(false);
248                },
249                // default sizing implementation
250                resize: function(){
251                        if(this.scrollboxNode){
252                                this.windowHeight = this.scrollboxNode.clientHeight;
253                        }
254                        for(var i=0; i<this.colCount; i++){
255                                //We want to have 1px in height min to keep scroller.  Otherwise can't scroll
256                                //and see header in empty grid.
257                                util.setStyleHeightPx(this.contentNodes[i], Math.max(1,this.height));
258                        }
259                       
260                        // Calculate the average row height and update the defaults (row and page).
261                        var needPage = (!this._invalidating);
262                        if(!needPage){
263                                var ah = this.grid.get("autoHeight");
264                                if(typeof ah == "number" && ah <= Math.min(this.rowsPerPage, this.rowCount)){
265                                        needPage = true;
266                                }
267                        }
268                        if(needPage){
269                                this.needPage(this.page, this.pageTop);
270                        }
271                        var rowsOnPage = (this.page < this.pageCount - 1) ? this.rowsPerPage : ((this.rowCount % this.rowsPerPage) || this.rowsPerPage);
272                        var pageHeight = this.getPageHeight(this.page);
273                        this.averageRowHeight = (pageHeight > 0 && rowsOnPage > 0) ? (pageHeight / rowsOnPage) : 0;
274                },
275                calcLastPageHeight: function(){
276                        if(!this.pageCount){
277                                return 0;
278                        }
279                        var lastPage = this.pageCount - 1;
280                        var lastPageHeight = ((this.rowCount % this.rowsPerPage)||(this.rowsPerPage)) * this.defaultRowHeight;
281                        this.pageHeights[lastPage] = lastPageHeight;
282                        return lastPageHeight;
283                },
284                updateContentHeight: function(inDh){
285                        this.height += inDh;
286                        this.resize();
287                },
288                updatePageHeight: function(inPageIndex, fromBuild, fromAsynRendering){
289                        if(this.pageExists(inPageIndex)){
290                                var oh = this.getPageHeight(inPageIndex);
291                                var h = (this.measurePage(inPageIndex));
292                                if(h === undefined){
293                                        h = oh;
294                                }
295                                this.pageHeights[inPageIndex] = h;
296                                if(oh != h){
297                                        this.updateContentHeight(h - oh);
298                                        var ah = this.grid.get("autoHeight");
299                                        if((typeof ah == "number" && ah > this.rowCount)||(ah === true && !fromBuild)){
300                                                if(!fromAsynRendering){
301                                                        this.grid.sizeChange();
302                                                }else{//fix #11101 by using fromAsynRendering to avoid deadlock
303                                                        var ns = this.grid.viewsNode.style;
304                                                        ns.height = parseInt(ns.height) + h - oh + 'px';
305                                                        this.repositionPages(inPageIndex);
306                                                }
307                                        }else{
308                                                this.repositionPages(inPageIndex);
309                                        }
310                                }
311                                return h;
312                        }
313                        return 0;
314                },
315                rowHeightChanged: function(inRowIndex, fromAsynRendering){
316                        this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage), false, fromAsynRendering);
317                },
318                // scroller core
319                invalidateNodes: function(){
320                        while(this.stack.length){
321                                this.destroyPage(this.popPage());
322                        }
323                },
324                createPageNode: function(){
325                        var p = document.createElement('div');
326                        html.attr(p,"role","presentation");
327                        p.style.position = 'absolute';
328                        //p.style.width = '100%';
329                        p.style[this.grid.isLeftToRight() ? "left" : "right"] = '0';
330                        return p;
331                },
332                getPageHeight: function(inPageIndex){
333                        var ph = this.pageHeights[inPageIndex];
334                        return (ph !== undefined ? ph : this.defaultPageHeight);
335                },
336                // FIXME: this is not a stack, it's a FIFO list
337                pushPage: function(inPageIndex){
338                        return this.stack.push(inPageIndex);
339                },
340                popPage: function(){
341                        return this.stack.shift();
342                },
343                findPage: function(inTop){
344                        var i = 0, h = 0;
345                        for(var ph = 0; i<this.pageCount; i++, h += ph){
346                                ph = this.getPageHeight(i);
347                                if(h + ph >= inTop){
348                                        break;
349                                }
350                        }
351                        this.page = i;
352                        this.pageTop = h;
353                },
354                buildPage: function(inPageIndex, inReuseNode, inPos){
355                        this.preparePage(inPageIndex, inReuseNode);
356                        this.positionPage(inPageIndex, inPos);
357                        // order of operations is key below
358                        this.installPage(inPageIndex);
359                        this.renderPage(inPageIndex);
360                        // order of operations is key above
361                        this.pushPage(inPageIndex);
362                },
363                needPage: function(inPageIndex, inPos){
364                        var h = this.getPageHeight(inPageIndex), oh = h;
365                        if(!this.pageExists(inPageIndex)){
366                                this.buildPage(inPageIndex, (!this.grid._autoHeight/*fix #10543*/ && this.keepPages&&(this.stack.length >= this.keepPages)), inPos);
367                                h = this.updatePageHeight(inPageIndex, true);
368                        }else{
369                                this.positionPage(inPageIndex, inPos);
370                        }
371                        return h;
372                },
373                onscroll: function(){
374                        this.scroll(this.scrollboxNode.scrollTop);
375                },
376                scroll: function(inTop){
377                        this.grid.scrollTop = inTop;
378                        if(this.colCount){
379                                this.startPacify();
380                                this.findPage(inTop);
381                                var h = this.height;
382                                var b = this.getScrollBottom(inTop);
383                                for(var p=this.page, y=this.pageTop; (p<this.pageCount)&&((b<0)||(y<b)); p++){
384                                        y += this.needPage(p, y);
385                                }
386                                this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop);
387                                this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
388                                // indicates some page size has been updated
389                                if(h != this.height){
390                                        this.repositionPages(p-1);
391                                }
392                                this.endPacify();
393                        }
394                },
395                getScrollBottom: function(inTop){
396                        return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
397                },
398                // events
399                processNodeEvent: function(e, inNode){
400                        var t = e.target;
401                        while(t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)){
402                                t = t.parentNode;
403                        }
404                        if(!t || !t.parentNode || (t.parentNode.parentNode != inNode)){
405                                return false;
406                        }
407                        var page = t.parentNode;
408                        e.topRowIndex = page.pageIndex * this.rowsPerPage;
409                        e.rowIndex = e.topRowIndex + indexInParent(t);
410                        e.rowTarget = t;
411                        return true;
412                },
413                processEvent: function(e){
414                        return this.processNodeEvent(e, this.contentNode);
415                },
416                // virtual rendering interface
417                renderRow: function(inRowIndex, inPageNode){
418                },
419                removeRow: function(inRowIndex){
420                },
421                // page node operations
422                getDefaultPageNode: function(inPageIndex){
423                        return this.getDefaultNodes()[inPageIndex];
424                },
425                positionPageNode: function(inNode, inPos){
426                },
427                getPageNodePosition: function(inNode){
428                        return inNode.offsetTop;
429                },
430                invalidatePageNode: function(inPageIndex, inNodes){
431                        var p = inNodes[inPageIndex];
432                        if(p){
433                                delete inNodes[inPageIndex];
434                                this.removePage(inPageIndex, p);
435                                cleanNode(p);
436                                p.innerHTML = '';
437                        }
438                        return p;
439                },
440                // scroll control
441                getPageRow: function(inPage){
442                        return inPage * this.rowsPerPage;
443                },
444                getLastPageRow: function(inPage){
445                        return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
446                },
447                getFirstVisibleRow: function(inPage, inPageTop, inScrollTop){
448                        if(!this.pageExists(inPage)){
449                                return 0;
450                        }
451                        var row = this.getPageRow(inPage);
452                        var nodes = this.getDefaultNodes();
453                        var rows = divkids(nodes[inPage]);
454                        for(var i=0,l=rows.length; i<l && inPageTop<inScrollTop; i++, row++){
455                                inPageTop += rows[i].offsetHeight;
456                        }
457                        return (row ? row - 1 : row);
458                },
459                getLastVisibleRow: function(inPage, inBottom, inScrollBottom){
460                        if(!this.pageExists(inPage)){
461                                return 0;
462                        }
463                        var nodes = this.getDefaultNodes();
464                        var row = this.getLastPageRow(inPage);
465                        var rows = divkids(nodes[inPage]);
466                        for(var i=rows.length-1; i>=0 && inBottom>inScrollBottom; i--, row--){
467                                inBottom -= rows[i].offsetHeight;
468                        }
469                        return row + 1;
470                },
471                findTopRow: function(inScrollTop){
472                        var nodes = this.getDefaultNodes();
473                        var rows = divkids(nodes[this.page]);
474                        for(var i=0,l=rows.length,t=this.pageTop,h; i<l; i++){
475                                h = rows[i].offsetHeight;
476                                t += h;
477                                if(t >= inScrollTop){
478                                        this.offset = h - (t - inScrollTop);
479                                        return i + this.page * this.rowsPerPage;
480                                }
481                        }
482                        return -1;
483                },
484                findScrollTop: function(inRow){
485                        var rowPage = Math.floor(inRow / this.rowsPerPage);
486                        var t = 0;
487                        var i, l;
488                        for(i=0; i<rowPage; i++){
489                                t += this.getPageHeight(i);
490                        }
491                        this.pageTop = t;
492                        this.page = rowPage;//fix #10543
493                        this.needPage(rowPage, this.pageTop);
494
495                        var nodes = this.getDefaultNodes();
496                        var rows = divkids(nodes[rowPage]);
497                        var r = inRow - this.rowsPerPage * rowPage;
498                        for(i=0,l=rows.length; i<l && i<r; i++){
499                                t += rows[i].offsetHeight;
500                        }
501                        return t;
502                },
503                dummy: 0
504        });
505});
Note: See TracBrowser for help on using the repository browser.