Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / PagingScroller.js
diff --git a/src/grid/PagingScroller.js b/src/grid/PagingScroller.js
new file mode 100644 (file)
index 0000000..9c322fc
--- /dev/null
@@ -0,0 +1,250 @@
+/**
+ * @class Ext.grid.PagingScroller
+ * @extends Ext.grid.Scroller
+ *
+ * @private
+ */
+Ext.define('Ext.grid.PagingScroller', {
+    extend: 'Ext.grid.Scroller',
+    alias: 'widget.paginggridscroller',
+    //renderTpl: null,
+    //tpl: [
+    //    '<tpl for="pages">',
+    //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
+    //    '</tpl>'
+    //],
+    /**
+     * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
+     * at what percentage to begin fetching the next page. For example if the pageSize is 100
+     * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
+     * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
+     */
+    percentageFromEdge: 0.35,
+    
+    /**
+     * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
+     * when scrolling the PagingScrollbar.
+     */
+    scrollToLoadBuffer: 200,
+    
+    activePrefetch: true,
+    
+    chunkSize: 50,
+    snapIncrement: 25,
+    
+    syncScroll: true,
+    
+    initComponent: function() {
+        var me = this,
+            ds = me.store;
+
+        ds.on('guaranteedrange', this.onGuaranteedRange, this);
+        this.callParent(arguments);
+    },
+    
+    
+    onGuaranteedRange: function(range, start, end) {
+        var me = this,
+            ds = me.store,
+            rs;
+        // this should never happen
+        if (range.length && me.visibleStart < range[0].index) {
+            return;
+        }
+        
+        ds.loadRecords(range);
+
+        if (!me.firstLoad) {
+            if (me.rendered) {
+                me.invalidate();
+            } else {
+                me.on('afterrender', this.invalidate, this, {single: true});
+            }
+            me.firstLoad = true;
+        } else {
+            // adjust to visible
+            me.syncTo();
+        }
+    },
+    
+    syncTo: function() {
+        var me            = this,
+            pnl           = me.getPanel(),
+            store         = pnl.store,
+            scrollerElDom = this.el.dom,
+            rowOffset     = me.visibleStart - store.guaranteedStart,
+            scrollBy      = rowOffset * me.rowHeight,
+            scrollHeight  = scrollerElDom.scrollHeight,
+            clientHeight  = scrollerElDom.clientHeight,
+            scrollTop     = scrollerElDom.scrollTop,
+            useMaximum;
+        
+        // BrowserBug: clientHeight reports 0 in IE9 StrictMode
+        // Instead we are using offsetHeight and hardcoding borders
+        if (Ext.isIE9 && Ext.isStrict) {
+            clientHeight = scrollerElDom.offsetHeight + 2;
+        }
+
+        // This should always be zero or greater than zero but staying
+        // safe and less than 0 we'll scroll to the bottom.        
+        useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
+        this.setViewScrollTop(scrollBy, useMaximum);
+    },
+    
+    getPageData : function(){
+        var panel = this.getPanel(),
+            store = panel.store,
+            totalCount = store.getTotalCount();
+            
+        return {
+            total : totalCount,
+            currentPage : store.currentPage,
+            pageCount: Math.ceil(totalCount / store.pageSize),
+            fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
+            toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
+        };
+    },
+    
+    onElScroll: function(e, t) {
+        var me = this,
+            panel = me.getPanel(),
+            store = panel.store,
+            pageSize = store.pageSize,
+            guaranteedStart = store.guaranteedStart,
+            guaranteedEnd = store.guaranteedEnd,
+            totalCount = store.getTotalCount(),
+            numFromEdge = Math.ceil(me.percentageFromEdge * store.pageSize),
+            position = t.scrollTop,
+            visibleStart = Math.floor(position / me.rowHeight),
+            view = panel.down('tableview'),
+            viewEl = view.el,
+            visibleHeight = viewEl.getHeight(),
+            visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
+            visibleEnd = visibleStart + visibleAhead,
+            prevPage = Math.floor(visibleStart / store.pageSize),
+            nextPage = Math.floor(visibleEnd / store.pageSize) + 2,
+            lastPage = Math.ceil(totalCount / store.pageSize),
+            //requestStart = visibleStart,
+            requestStart = Math.floor(visibleStart / me.snapIncrement) * me.snapIncrement,
+            requestEnd = requestStart + pageSize - 1,
+            activePrefetch = me.activePrefetch;
+
+        me.visibleStart = visibleStart;
+        me.visibleEnd = visibleEnd;
+        
+        
+        me.syncScroll = true;
+        if (totalCount >= pageSize) {
+            // end of request was past what the total is, grab from the end back a pageSize
+            if (requestEnd > totalCount - 1) {
+                this.cancelLoad();
+                if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
+                    me.syncScroll = true;
+                }
+                store.guaranteeRange(totalCount - pageSize, totalCount - 1);
+            // Out of range, need to reset the current data set
+            } else if (visibleStart < guaranteedStart || visibleEnd > guaranteedEnd) {
+                if (store.rangeSatisfied(requestStart, requestEnd)) {
+                    this.cancelLoad();
+                    store.guaranteeRange(requestStart, requestEnd);
+                } else {
+                    store.mask();
+                    me.attemptLoad(requestStart, requestEnd);
+                }
+                // dont sync the scroll view immediately, sync after the range has been guaranteed
+                me.syncScroll = false;
+            } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
+                me.syncScroll = true;
+                store.prefetchPage(prevPage);
+            } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
+                me.syncScroll = true;
+                store.prefetchPage(nextPage);
+            }
+        }
+    
+    
+        if (me.syncScroll) {
+            me.syncTo();
+        }
+    },
+    
+    getSizeCalculation: function() {
+        // Use the direct ownerCt here rather than the scrollerOwner
+        // because we are calculating widths/heights.
+        var owner = this.ownerCt,
+            view   = owner.getView(),
+            store  = this.store,
+            dock   = this.dock,
+            elDom  = this.el.dom,
+            width  = 1,
+            height = 1;
+        
+        if (!this.rowHeight) {
+            this.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
+        }
+
+        height = store.getTotalCount() * this.rowHeight;
+
+        if (isNaN(width)) {
+            width = 1;
+        }
+        if (isNaN(height)) {
+            height = 1;
+        }
+        return {
+            width: width,
+            height: height
+        };
+    },
+    
+    attemptLoad: function(start, end) {
+        var me = this;
+        if (!me.loadTask) {
+            me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
+        }
+        me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
+    },
+    
+    cancelLoad: function() {
+        if (this.loadTask) {
+            this.loadTask.cancel();
+        }
+    },
+    
+    doAttemptLoad:  function(start, end) {
+        var store = this.getPanel().store;
+        store.guaranteeRange(start, end);
+    },
+    
+    setViewScrollTop: function(scrollTop, useMax) {
+        var owner = this.getPanel(),
+            items = owner.query('tableview'),
+            i = 0,
+            len = items.length,
+            center,
+            centerEl,
+            calcScrollTop,
+            maxScrollTop,
+            scrollerElDom = this.el.dom;
+            
+        owner.virtualScrollTop = scrollTop;
+            
+        center = items[1] || items[0];
+        centerEl = center.el.dom;
+        
+        maxScrollTop = ((owner.store.pageSize * this.rowHeight) - centerEl.clientHeight);
+        calcScrollTop = (scrollTop % ((owner.store.pageSize * this.rowHeight) + 1));
+        if (useMax) {
+            calcScrollTop = maxScrollTop;
+        }
+        if (calcScrollTop > maxScrollTop) {
+            //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
+            return;
+            // calcScrollTop = maxScrollTop;
+        }
+        for (; i < len; i++) {
+            items[i].el.dom.scrollTop = calcScrollTop;
+        }
+    }
+});
+