source: Dev/branches/rest-dojo-ui/client/dojox/grid/_Scroller.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: 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.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                                var height = this.grid.rowHeight + 1;
155                                return ((inPageIndex + 1) * this.rowsPerPage > this.rowCount ?
156                                        this.rowCount - inPageIndex * this.rowsPerPage :
157                                        this.rowsPerPage) * height;
158                                         
159                        }
160                        var n = this.getDefaultPageNode(inPageIndex);
161                        return (n && n.innerHTML) ? n.offsetHeight : undefined;
162                },
163                positionPage: function(inPageIndex, inPos){
164                        for(var i=0; i<this.colCount; i++){
165                                this.pageNodes[i][inPageIndex].style.top = inPos + 'px';
166                        }
167                },
168                repositionPages: function(inPageIndex){
169                        var nodes = this.getDefaultNodes();
170                        var last = 0;
171
172                        for(var i=0; i<this.stack.length; i++){
173                                last = Math.max(this.stack[i], last);
174                        }
175                        //
176                        var n = nodes[inPageIndex];
177                        var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0);
178                        for(var p=inPageIndex+1; p<=last; p++){
179                                n = nodes[p];
180                                if(n){
181                                        if(this.getPageNodePosition(n) == y){
182                                                return;
183                                        }
184                                        this.positionPage(p, y);
185                                }
186                                y += this.getPageHeight(p);
187                        }
188                },
189                installPage: function(inPageIndex){
190                        for(var i=0; i<this.colCount; i++){
191                                this.contentNodes[i].appendChild(this.pageNodes[i][inPageIndex]);
192                        }
193                },
194                preparePage: function(inPageIndex, inReuseNode){
195                        var p = (inReuseNode ? this.popPage() : null);
196                        for(var i=0; i<this.colCount; i++){
197                                var nodes = this.pageNodes[i];
198                                var new_p = (p === null ? this.createPageNode() : this.invalidatePageNode(p, nodes));
199                                new_p.pageIndex = inPageIndex;
200                                nodes[inPageIndex] = new_p;
201                        }
202                },
203                // rendering implementation
204                renderPage: function(inPageIndex){
205                        var nodes = [];
206                        var i, j;
207                        for(i=0; i<this.colCount; i++){
208                                nodes[i] = this.pageNodes[i][inPageIndex];
209                        }
210                        for(i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
211                                this.renderRow(j, nodes);
212                        }
213                },
214                removePage: function(inPageIndex){
215                        for(var i=0, j=inPageIndex*this.rowsPerPage; i<this.rowsPerPage; i++, j++){
216                                this.removeRow(j);
217                        }
218                },
219                destroyPage: function(inPageIndex){
220                        for(var i=0; i<this.colCount; i++){
221                                var n = this.invalidatePageNode(inPageIndex, this.pageNodes[i]);
222                                if(n){
223                                        html.destroy(n);
224                                }
225                        }
226                },
227                pacify: function(inShouldPacify){
228                },
229                // pacification
230                pacifying: false,
231                pacifyTicks: 200,
232                setPacifying: function(inPacifying){
233                        if(this.pacifying != inPacifying){
234                                this.pacifying = inPacifying;
235                                this.pacify(this.pacifying);
236                        }
237                },
238                startPacify: function(){
239                        this.startPacifyTicks = new Date().getTime();
240                },
241                doPacify: function(){
242                        var result = (new Date().getTime() - this.startPacifyTicks) > this.pacifyTicks;
243                        this.setPacifying(true);
244                        this.startPacify();
245                        return result;
246                },
247                endPacify: function(){
248                        this.setPacifying(false);
249                },
250                // default sizing implementation
251                resize: function(){
252                        if(this.scrollboxNode){
253                                this.windowHeight = this.scrollboxNode.clientHeight;
254                        }
255                        for(var i=0; i<this.colCount; i++){
256                                //We want to have 1px in height min to keep scroller.  Otherwise can't scroll
257                                //and see header in empty grid.
258                                util.setStyleHeightPx(this.contentNodes[i], Math.max(1,this.height));
259                        }
260                       
261                        // Calculate the average row height and update the defaults (row and page).
262                        var needPage = (!this._invalidating);
263                        if(!needPage){
264                                var ah = this.grid.get("autoHeight");
265                                if(typeof ah == "number" && ah <= Math.min(this.rowsPerPage, this.rowCount)){
266                                        needPage = true;
267                                }
268                        }
269                        if(needPage){
270                                this.needPage(this.page, this.pageTop);
271                        }
272                        var rowsOnPage = (this.page < this.pageCount - 1) ? this.rowsPerPage : ((this.rowCount % this.rowsPerPage) || this.rowsPerPage);
273                        var pageHeight = this.getPageHeight(this.page);
274                        this.averageRowHeight = (pageHeight > 0 && rowsOnPage > 0) ? (pageHeight / rowsOnPage) : 0;
275                },
276                calcLastPageHeight: function(){
277                        if(!this.pageCount){
278                                return 0;
279                        }
280                        var lastPage = this.pageCount - 1;
281                        var lastPageHeight = ((this.rowCount % this.rowsPerPage)||(this.rowsPerPage)) * this.defaultRowHeight;
282                        this.pageHeights[lastPage] = lastPageHeight;
283                        return lastPageHeight;
284                },
285                updateContentHeight: function(inDh){
286                        this.height += inDh;
287                        this.resize();
288                },
289                updatePageHeight: function(inPageIndex, fromBuild, fromAsynRendering){
290                        if(this.pageExists(inPageIndex)){
291                                var oh = this.getPageHeight(inPageIndex);
292                                var h = (this.measurePage(inPageIndex));
293                                if(h === undefined){
294                                        h = oh;
295                                }
296                                this.pageHeights[inPageIndex] = h;
297                                if(oh != h){
298                                        this.updateContentHeight(h - oh);
299                                        var ah = this.grid.get("autoHeight");
300                                        if((typeof ah == "number" && ah > this.rowCount)||(ah === true && !fromBuild)){
301                                                if(!fromAsynRendering){
302                                                        this.grid.sizeChange();
303                                                }else{//fix #11101 by using fromAsynRendering to avoid deadlock
304                                                        var ns = this.grid.viewsNode.style;
305                                                        ns.height = parseInt(ns.height) + h - oh + 'px';
306                                                        this.repositionPages(inPageIndex);
307                                                }
308                                        }else{
309                                                this.repositionPages(inPageIndex);
310                                        }
311                                }
312                                return h;
313                        }
314                        return 0;
315                },
316                rowHeightChanged: function(inRowIndex, fromAsynRendering){
317                        this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage), false, fromAsynRendering);
318                },
319                // scroller core
320                invalidateNodes: function(){
321                        while(this.stack.length){
322                                this.destroyPage(this.popPage());
323                        }
324                },
325                createPageNode: function(){
326                        var p = document.createElement('div');
327                        html.attr(p,"role","presentation");
328                        p.style.position = 'absolute';
329                        //p.style.width = '100%';
330                        p.style[this.grid.isLeftToRight() ? "left" : "right"] = '0';
331                        return p;
332                },
333                getPageHeight: function(inPageIndex){
334                        var ph = this.pageHeights[inPageIndex];
335                        return (ph !== undefined ? ph : this.defaultPageHeight);
336                },
337                // FIXME: this is not a stack, it's a FIFO list
338                pushPage: function(inPageIndex){
339                        return this.stack.push(inPageIndex);
340                },
341                popPage: function(){
342                        return this.stack.shift();
343                },
344                findPage: function(inTop){
345                        var i = 0, h = 0;
346                        for(var ph = 0; i<this.pageCount; i++, h += ph){
347                                ph = this.getPageHeight(i);
348                                if(h + ph >= inTop){
349                                        break;
350                                }
351                        }
352                        this.page = i;
353                        this.pageTop = h;
354                },
355                buildPage: function(inPageIndex, inReuseNode, inPos){
356                        this.preparePage(inPageIndex, inReuseNode);
357                        this.positionPage(inPageIndex, inPos);
358                        // order of operations is key below
359                        this.installPage(inPageIndex);
360                        this.renderPage(inPageIndex);
361                        // order of operations is key above
362                        this.pushPage(inPageIndex);
363                },
364                needPage: function(inPageIndex, inPos){
365                        var h = this.getPageHeight(inPageIndex), oh = h;
366                        if(!this.pageExists(inPageIndex)){
367                                this.buildPage(inPageIndex, (!this.grid._autoHeight/*fix #10543*/ && this.keepPages&&(this.stack.length >= this.keepPages)), inPos);
368                                h = this.updatePageHeight(inPageIndex, true);
369                        }else{
370                                this.positionPage(inPageIndex, inPos);
371                        }
372                        return h;
373                },
374                onscroll: function(){
375                        this.scroll(this.scrollboxNode.scrollTop);
376                },
377                scroll: function(inTop){
378                        this.grid.scrollTop = inTop;
379                        if(this.colCount){
380                                this.startPacify();
381                                this.findPage(inTop);
382                                var h = this.height;
383                                var b = this.getScrollBottom(inTop);
384                                for(var p=this.page, y=this.pageTop; (p<this.pageCount)&&((b<0)||(y<b)); p++){
385                                        y += this.needPage(p, y);
386                                }
387                                this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop);
388                                this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
389                                // indicates some page size has been updated
390                                if(h != this.height){
391                                        this.repositionPages(p-1);
392                                }
393                                this.endPacify();
394                        }
395                },
396                getScrollBottom: function(inTop){
397                        return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
398                },
399                // events
400                processNodeEvent: function(e, inNode){
401                        var t = e.target;
402                        while(t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)){
403                                t = t.parentNode;
404                        }
405                        if(!t || !t.parentNode || (t.parentNode.parentNode != inNode)){
406                                return false;
407                        }
408                        var page = t.parentNode;
409                        e.topRowIndex = page.pageIndex * this.rowsPerPage;
410                        e.rowIndex = e.topRowIndex + indexInParent(t);
411                        e.rowTarget = t;
412                        return true;
413                },
414                processEvent: function(e){
415                        return this.processNodeEvent(e, this.contentNode);
416                },
417                // virtual rendering interface
418                renderRow: function(inRowIndex, inPageNode){
419                },
420                removeRow: function(inRowIndex){
421                },
422                // page node operations
423                getDefaultPageNode: function(inPageIndex){
424                        return this.getDefaultNodes()[inPageIndex];
425                },
426                positionPageNode: function(inNode, inPos){
427                },
428                getPageNodePosition: function(inNode){
429                        return inNode.offsetTop;
430                },
431                invalidatePageNode: function(inPageIndex, inNodes){
432                        var p = inNodes[inPageIndex];
433                        if(p){
434                                delete inNodes[inPageIndex];
435                                this.removePage(inPageIndex, p);
436                                cleanNode(p);
437                                p.innerHTML = '';
438                        }
439                        return p;
440                },
441                // scroll control
442                getPageRow: function(inPage){
443                        return inPage * this.rowsPerPage;
444                },
445                getLastPageRow: function(inPage){
446                        return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
447                },
448                getFirstVisibleRow: function(inPage, inPageTop, inScrollTop){
449                        if(!this.pageExists(inPage)){
450                                return 0;
451                        }
452                        var row = this.getPageRow(inPage);
453                        var nodes = this.getDefaultNodes();
454                        var rows = divkids(nodes[inPage]);
455                        for(var i=0,l=rows.length; i<l && inPageTop<inScrollTop; i++, row++){
456                                inPageTop += rows[i].offsetHeight;
457                        }
458                        return (row ? row - 1 : row);
459                },
460                getLastVisibleRow: function(inPage, inBottom, inScrollBottom){
461                        if(!this.pageExists(inPage)){
462                                return 0;
463                        }
464                        var nodes = this.getDefaultNodes();
465                        var row = this.getLastPageRow(inPage);
466                        var rows = divkids(nodes[inPage]);
467                        for(var i=rows.length-1; i>=0 && inBottom>inScrollBottom; i--, row--){
468                                inBottom -= rows[i].offsetHeight;
469                        }
470                        return row + 1;
471                },
472                findTopRow: function(inScrollTop){
473                        var nodes = this.getDefaultNodes();
474                        var rows = divkids(nodes[this.page]);
475                        for(var i=0,l=rows.length,t=this.pageTop,h; i<l; i++){
476                                h = rows[i].offsetHeight;
477                                t += h;
478                                if(t >= inScrollTop){
479                                        this.offset = h - (t - inScrollTop);
480                                        return i + this.page * this.rowsPerPage;
481                                }
482                        }
483                        return -1;
484                },
485                findScrollTop: function(inRow){
486                        var rowPage = Math.floor(inRow / this.rowsPerPage);
487                        var t = 0;
488                        var i, l;
489                        for(i=0; i<rowPage; i++){
490                                t += this.getPageHeight(i);
491                        }
492                        this.pageTop = t;
493                        this.page = rowPage;//fix #10543
494                        this.needPage(rowPage, this.pageTop);
495
496                        var nodes = this.getDefaultNodes();
497                        var rows = divkids(nodes[rowPage]);
498                        var r = inRow - this.rowsPerPage * rowPage;
499                        for(i=0,l=rows.length; i<l && i<r; i++){
500                                t += rows[i].offsetHeight;
501                        }
502                        return t;
503                },
504                dummy: 0
505        });
506});
Note: See TracBrowser for help on using the repository browser.