Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource['dojox.grid._grid.scroller']){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource['dojox.grid._grid.scroller'] = true;
dojo.provide('dojox.grid._grid.scroller');

dojo.declare('dojox.grid.scroller.base', null, {
        // summary:
        //      virtual scrollbox, abstract class
        //      Content must in /rows/
        //      Rows are managed in contiguous sets called /pages/
        //      There are a fixed # of rows per page
        //      The minimum rendered unit is a page
        constructor: function(){
                this.pageHeights = [];
                this.stack = [];
        },
        // specified
        rowCount: 0, // total number of rows to manage
        defaultRowHeight: 10, // default height of a row
        keepRows: 100, // maximum number of rows that should exist at one time
        contentNode: null, // node to contain pages
        scrollboxNode: null, // node that controls scrolling
        // calculated
        defaultPageHeight: 0, // default height of a page
        keepPages: 10, // maximum number of pages that should exists at one time
        pageCount: 0,
        windowHeight: 0,
        firstVisibleRow: 0,
        lastVisibleRow: 0,
        // private
        page: 0,
        pageTop: 0,
        // init
        init: function(inRowCount, inKeepRows, inRowsPerPage){
                switch(arguments.length){
                        case 3: this.rowsPerPage = inRowsPerPage;
                        case 2: this.keepRows = inKeepRows;
                        case 1: this.rowCount = inRowCount;
                }
                this.defaultPageHeight = this.defaultRowHeight * this.rowsPerPage;
                //this.defaultPageHeight = this.defaultRowHeight * Math.min(this.rowsPerPage, this.rowCount);
                this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
                this.keepPages = Math.max(Math.ceil(this.keepRows / this.rowsPerPage), 2);
                this.invalidate();
                if(this.scrollboxNode){
                        this.scrollboxNode.scrollTop = 0;
                        this.scroll(0);
                        this.scrollboxNode.onscroll = dojo.hitch(this, 'onscroll');
                }
        },
        // updating
        invalidate: function(){
                this.invalidateNodes();
                this.pageHeights = [];
                this.height = (this.pageCount ? (this.pageCount - 1)* this.defaultPageHeight + this.calcLastPageHeight() : 0);
                this.resize();
        },
        updateRowCount: function(inRowCount){
                this.invalidateNodes();
                this.rowCount = inRowCount;
                // update page count, adjust document height
                oldPageCount = this.pageCount;
                this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
                if(this.pageCount < oldPageCount){
                        for(var i=oldPageCount-1; i>=this.pageCount; i--){
                                this.height -= this.getPageHeight(i);
                                delete this.pageHeights[i]
                        }
                }else if(this.pageCount > oldPageCount){
                        this.height += this.defaultPageHeight * (this.pageCount - oldPageCount - 1) + this.calcLastPageHeight();
                }
                this.resize();
        },
        // abstract interface
        pageExists: function(inPageIndex){
        },
        measurePage: function(inPageIndex){
        },
        positionPage: function(inPageIndex, inPos){
        },
        repositionPages: function(inPageIndex){
        },
        installPage: function(inPageIndex){
        },
        preparePage: function(inPageIndex, inPos, inReuseNode){
        },
        renderPage: function(inPageIndex){
        },
        removePage: function(inPageIndex){
        },
        pacify: function(inShouldPacify){
        },
        // pacification
        pacifying: false,
        pacifyTicks: 200,
        setPacifying: function(inPacifying){
                if(this.pacifying != inPacifying){
                        this.pacifying = inPacifying;
                        this.pacify(this.pacifying);
                }
        },
        startPacify: function(){
                this.startPacifyTicks = new Date().getTime();
        },
        doPacify: function(){
                var result = (new Date().getTime() - this.startPacifyTicks) > this.pacifyTicks;
                this.setPacifying(true);
                this.startPacify();
                return result;
        },
        endPacify: function(){
                this.setPacifying(false);
        },
        // default sizing implementation
        resize: function(){
                if(this.scrollboxNode){
                        this.windowHeight = this.scrollboxNode.clientHeight;
                }
                dojox.grid.setStyleHeightPx(this.contentNode, this.height);
        },
        calcLastPageHeight: function(){
                if(!this.pageCount){
                        return 0;
                }
                var lastPage = this.pageCount - 1;
                var lastPageHeight = ((this.rowCount % this.rowsPerPage)||(this.rowsPerPage)) * this.defaultRowHeight;
                this.pageHeights[lastPage] = lastPageHeight;
                return lastPageHeight;
        },
        updateContentHeight: function(inDh){
                this.height += inDh;
                this.resize();
        },
        updatePageHeight: function(inPageIndex){
                if(this.pageExists(inPageIndex)){
                        var oh = this.getPageHeight(inPageIndex);
                        var h = (this.measurePage(inPageIndex))||(oh);
                        this.pageHeights[inPageIndex] = h;
                        if((h)&&(oh != h)){
                                this.updateContentHeight(h - oh)
                                this.repositionPages(inPageIndex);
                        }
                }
        },
        rowHeightChanged: function(inRowIndex){
                this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage));
        },
        // scroller core
        invalidateNodes: function(){
                while(this.stack.length){
                        this.destroyPage(this.popPage());
                }
        },
        createPageNode: function(){
                var p = document.createElement('div');
                p.style.position = 'absolute';
                //p.style.width = '100%';
                p.style.left = '0';
                return p;
        },
        getPageHeight: function(inPageIndex){
                var ph = this.pageHeights[inPageIndex];
                return (ph !== undefined ? ph : this.defaultPageHeight);
        },
        // FIXME: this is not a stack, it's a FIFO list
        pushPage: function(inPageIndex){
                return this.stack.push(inPageIndex);
        },
        popPage: function(){
                return this.stack.shift();
        },
        findPage: function(inTop){
                var i = 0, h = 0;
                for(var ph = 0; i<this.pageCount; i++, h += ph){
                        ph = this.getPageHeight(i);
                        if(h + ph >= inTop){
                                break;
                        }
                }
                this.page = i;
                this.pageTop = h;
        },
        buildPage: function(inPageIndex, inReuseNode, inPos){
                this.preparePage(inPageIndex, inReuseNode);
                this.positionPage(inPageIndex, inPos);
                // order of operations is key below
                this.installPage(inPageIndex);
                this.renderPage(inPageIndex);
                // order of operations is key above
                this.pushPage(inPageIndex);
        },
        needPage: function(inPageIndex, inPos){
                var h = this.getPageHeight(inPageIndex), oh = h;
                if(!this.pageExists(inPageIndex)){
                        this.buildPage(inPageIndex, (this.keepPages)&&(this.stack.length >= this.keepPages), inPos);
                        h = this.measurePage(inPageIndex) || h;
                        this.pageHeights[inPageIndex] = h;
                        if(h && (oh != h)){
                                this.updateContentHeight(h - oh)
                        }
                }else{
                        this.positionPage(inPageIndex, inPos);
                }
                return h;
        },
        onscroll: function(){
                this.scroll(this.scrollboxNode.scrollTop);
        },
        scroll: function(inTop){
                this.startPacify();
                this.findPage(inTop);
                var h = this.height;
                var b = this.getScrollBottom(inTop);
                for(var p=this.page, y=this.pageTop; (p<this.pageCount)&&((b<0)||(y<b)); p++){
                        y += this.needPage(p, y);
                }
                this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop);
                this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
                // indicates some page size has been updated
                if(h != this.height){
                        this.repositionPages(p-1);
                }
                this.endPacify();
        },
        getScrollBottom: function(inTop){
                return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
        },
        // events
        processNodeEvent: function(e, inNode){
                var t = e.target;
                while(t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)){
                        t = t.parentNode;
                }
                if(!t || !t.parentNode || (t.parentNode.parentNode != inNode)){
                        return false;
                }
                var page = t.parentNode;
                e.topRowIndex = page.pageIndex * this.rowsPerPage;
                e.rowIndex = e.topRowIndex + dojox.grid.indexInParent(t);
                e.rowTarget = t;
                return true;
        },
        processEvent: function(e){
                return this.processNodeEvent(e, this.contentNode);
        },
        dummy: 0
});

dojo.declare('dojox.grid.scroller', dojox.grid.scroller.base, {
        // summary:
        //      virtual scroller class, makes no assumption about shape of items being scrolled
        constructor: function(){
                this.pageNodes = [];
        },
        // virtual rendering interface
        renderRow: function(inRowIndex, inPageNode){
        },
        removeRow: function(inRowIndex){
        },
        // page node operations
        getDefaultNodes: function(){
                return this.pageNodes;
        },
        getDefaultPageNode: function(inPageIndex){
                return this.getDefaultNodes()[inPageIndex];
        },
        positionPageNode: function(inNode, inPos){
                inNode.style.top = inPos + 'px';
        },
        getPageNodePosition: function(inNode){
                return inNode.offsetTop;
        },
        repositionPageNodes: function(inPageIndex, inNodes){
                var last = 0;
                for(var i=0; i<this.stack.length; i++){
                        last = Math.max(this.stack[i], last);
                }
                //
                var n = inNodes[inPageIndex];
                var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0);
                //console.log('detected height change, repositioning from #%d (%d) @ %d ', inPageIndex + 1, last, y, this.pageHeights[0]);
                //
                for(var p=inPageIndex+1; p<=last; p++){
                        n = inNodes[p];
                        if(n){
                                //console.log('#%d @ %d', inPageIndex, y, this.getPageNodePosition(n));
                                if(this.getPageNodePosition(n) == y){
                                        return;
                                }
                                //console.log('placing page %d at %d', p, y);
                                this.positionPage(p, y);
                        }
                        y += this.getPageHeight(p);
                }
        },
        invalidatePageNode: function(inPageIndex, inNodes){
                var p = inNodes[inPageIndex];
                if(p){
                        delete inNodes[inPageIndex];
                        this.removePage(inPageIndex, p);
                        dojox.grid.cleanNode(p);
                        p.innerHTML = '';
                }
                return p;
        },
        preparePageNode: function(inPageIndex, inReusePageIndex, inNodes){
                var p = (inReusePageIndex === null ? this.createPageNode() : this.invalidatePageNode(inReusePageIndex, inNodes));
                p.pageIndex = inPageIndex;
                p.id = 'page-' + inPageIndex;
                inNodes[inPageIndex] = p;
        },
        // implementation for page manager
        pageExists: function(inPageIndex){
                return Boolean(this.getDefaultPageNode(inPageIndex));
        },
        measurePage: function(inPageIndex){
                return this.getDefaultPageNode(inPageIndex).offsetHeight;
        },
        positionPage: function(inPageIndex, inPos){
                this.positionPageNode(this.getDefaultPageNode(inPageIndex), inPos);
        },
        repositionPages: function(inPageIndex){
                this.repositionPageNodes(inPageIndex, this.getDefaultNodes());
        },
        preparePage: function(inPageIndex, inReuseNode){
                this.preparePageNode(inPageIndex, (inReuseNode ? this.popPage() : null), this.getDefaultNodes());
        },
        installPage: function(inPageIndex){
                this.contentNode.appendChild(this.getDefaultPageNode(inPageIndex));
        },
        destroyPage: function(inPageIndex){
                var p = this.invalidatePageNode(inPageIndex, this.getDefaultNodes());
                dojox.grid.removeNode(p);
        },
        // rendering implementation
        renderPage: function(inPageIndex){
                var node = this.pageNodes[inPageIndex];
                for(var i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
                        this.renderRow(j, node);
                }
        },
        removePage: function(inPageIndex){
                for(var i=0, j=inPageIndex*this.rowsPerPage; i<this.rowsPerPage; i++, j++){
                        this.removeRow(j);
                }
        },
        // scroll control
        getPageRow: function(inPage){
                return inPage * this.rowsPerPage;
        },
        getLastPageRow: function(inPage){
                return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
        },
        getFirstVisibleRowNodes: function(inPage, inPageTop, inScrollTop, inNodes){
                var row = this.getPageRow(inPage);
                var rows = dojox.grid.divkids(inNodes[inPage]);
                for(var i=0,l=rows.length; i<l && inPageTop<inScrollTop; i++, row++){
                        inPageTop += rows[i].offsetHeight;
                }
                return (row ? row - 1 : row);
        },
        getFirstVisibleRow: function(inPage, inPageTop, inScrollTop){
                if(!this.pageExists(inPage)){
                        return 0;
                }
                return this.getFirstVisibleRowNodes(inPage, inPageTop, inScrollTop, this.getDefaultNodes());
        },
        getLastVisibleRowNodes: function(inPage, inBottom, inScrollBottom, inNodes){
                var row = this.getLastPageRow(inPage);
                var rows = dojox.grid.divkids(inNodes[inPage]);
                for(var i=rows.length-1; i>=0 && inBottom>inScrollBottom; i--, row--){
                        inBottom -= rows[i].offsetHeight;
                }
                return row + 1;
        },
        getLastVisibleRow: function(inPage, inBottom, inScrollBottom){
                if(!this.pageExists(inPage)){
                        return 0;
                }
                return this.getLastVisibleRowNodes(inPage, inBottom, inScrollBottom, this.getDefaultNodes());
        },
        findTopRowForNodes: function(inScrollTop, inNodes){
                var rows = dojox.grid.divkids(inNodes[this.page]);
                for(var i=0,l=rows.length,t=this.pageTop,h; i<l; i++){
                        h = rows[i].offsetHeight;
                        t += h;
                        if(t >= inScrollTop){
                                this.offset = h - (t - inScrollTop);
                                return i + this.page * this.rowsPerPage;
                        }
                }
                return -1;
        },
        findScrollTopForNodes: function(inRow, inNodes){
                var rowPage = Math.floor(inRow / this.rowsPerPage);
                var t = 0;
                for(var i=0; i<rowPage; i++){
                        t += this.getPageHeight(i);
                }
                this.pageTop = t;
                this.needPage(rowPage, this.pageTop);
                var rows = dojox.grid.divkids(inNodes[rowPage]);
                var r = inRow - this.rowsPerPage * rowPage;
                for(var i=0,l=rows.length; i<l && i<r; i++){
                        t += rows[i].offsetHeight;
                }
                return t;
        },
        findTopRow: function(inScrollTop){
                return this.findTopRowForNodes(inScrollTop, this.getDefaultNodes());
        },
        findScrollTop: function(inRow){
                return this.findScrollTopForNodes(inRow, this.getDefaultNodes());
        },
        dummy: 0
});

dojo.declare('dojox.grid.scroller.columns', dojox.grid.scroller, {
        // summary:
        //      Virtual scroller class that scrolls list of columns. Owned by grid and used internally
        //      for virtual scrolling.
        constructor: function(inContentNodes){
                this.setContentNodes(inContentNodes);
        },
        // nodes
        setContentNodes: function(inNodes){
                this.contentNodes = inNodes;
                this.colCount = (this.contentNodes ? this.contentNodes.length : 0);
                this.pageNodes = [];
                for(var i=0; i<this.colCount; i++){
                        this.pageNodes[i] = [];
                }
        },
        getDefaultNodes: function(){
                return this.pageNodes[0] || [];
        },
        scroll: function(inTop) {
                if(this.colCount){
                        dojox.grid.scroller.prototype.scroll.call(this, inTop);
                }
        },
        // resize
        resize: function(){
                if(this.scrollboxNode){
                        this.windowHeight = this.scrollboxNode.clientHeight;
                }
                for(var i=0; i<this.colCount; i++){
                        dojox.grid.setStyleHeightPx(this.contentNodes[i], this.height);
                }
        },
        // implementation for page manager
        positionPage: function(inPageIndex, inPos){
                for(var i=0; i<this.colCount; i++){
                        this.positionPageNode(this.pageNodes[i][inPageIndex], inPos);
                }
        },
        preparePage: function(inPageIndex, inReuseNode){
                var p = (inReuseNode ? this.popPage() : null);
                for(var i=0; i<this.colCount; i++){
                        this.preparePageNode(inPageIndex, p, this.pageNodes[i]);
                }
        },
        installPage: function(inPageIndex){
                for(var i=0; i<this.colCount; i++){
                        this.contentNodes[i].appendChild(this.pageNodes[i][inPageIndex]);
                }
        },
        destroyPage: function(inPageIndex){
                for(var i=0; i<this.colCount; i++){
                        dojox.grid.removeNode(this.invalidatePageNode(inPageIndex, this.pageNodes[i]));
                }
        },
        // rendering implementation
        renderPage: function(inPageIndex){
                var nodes = [];
                for(var i=0; i<this.colCount; i++){
                        nodes[i] = this.pageNodes[i][inPageIndex];
                }
                //this.renderRows(inPageIndex*this.rowsPerPage, this.rowsPerPage, nodes);
                for(var i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
                        this.renderRow(j, nodes);
                }
        }
});

}