Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / PagingScroller.js
1 /**
2  * @class Ext.grid.PagingScroller
3  * @extends Ext.grid.Scroller
4  *
5  * @private
6  */
7 Ext.define('Ext.grid.PagingScroller', {
8     extend: 'Ext.grid.Scroller',
9     alias: 'widget.paginggridscroller',
10     //renderTpl: null,
11     //tpl: [
12     //    '<tpl for="pages">',
13     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
14     //    '</tpl>'
15     //],
16     /**
17      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
18      * at what percentage to begin fetching the next page. For example if the pageSize is 100
19      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
20      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
21      */
22     percentageFromEdge: 0.35,
23     
24     /**
25      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
26      * when scrolling the PagingScrollbar.
27      */
28     scrollToLoadBuffer: 200,
29     
30     activePrefetch: true,
31     
32     chunkSize: 50,
33     snapIncrement: 25,
34     
35     syncScroll: true,
36     
37     initComponent: function() {
38         var me = this,
39             ds = me.store;
40
41         ds.on('guaranteedrange', this.onGuaranteedRange, this);
42         this.callParent(arguments);
43     },
44     
45     
46     onGuaranteedRange: function(range, start, end) {
47         var me = this,
48             ds = me.store,
49             rs;
50         // this should never happen
51         if (range.length && me.visibleStart < range[0].index) {
52             return;
53         }
54         
55         ds.loadRecords(range);
56
57         if (!me.firstLoad) {
58             if (me.rendered) {
59                 me.invalidate();
60             } else {
61                 me.on('afterrender', this.invalidate, this, {single: true});
62             }
63             me.firstLoad = true;
64         } else {
65             // adjust to visible
66             me.syncTo();
67         }
68     },
69     
70     syncTo: function() {
71         var me            = this,
72             pnl           = me.getPanel(),
73             store         = pnl.store,
74             scrollerElDom = this.el.dom,
75             rowOffset     = me.visibleStart - store.guaranteedStart,
76             scrollBy      = rowOffset * me.rowHeight,
77             scrollHeight  = scrollerElDom.scrollHeight,
78             clientHeight  = scrollerElDom.clientHeight,
79             scrollTop     = scrollerElDom.scrollTop,
80             useMaximum;
81         
82         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
83         // Instead we are using offsetHeight and hardcoding borders
84         if (Ext.isIE9 && Ext.isStrict) {
85             clientHeight = scrollerElDom.offsetHeight + 2;
86         }
87
88         // This should always be zero or greater than zero but staying
89         // safe and less than 0 we'll scroll to the bottom.        
90         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
91         this.setViewScrollTop(scrollBy, useMaximum);
92     },
93     
94     getPageData : function(){
95         var panel = this.getPanel(),
96             store = panel.store,
97             totalCount = store.getTotalCount();
98             
99         return {
100             total : totalCount,
101             currentPage : store.currentPage,
102             pageCount: Math.ceil(totalCount / store.pageSize),
103             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
105         };
106     },
107     
108     onElScroll: function(e, t) {
109         var me = this,
110             panel = me.getPanel(),
111             store = panel.store,
112             pageSize = store.pageSize,
113             guaranteedStart = store.guaranteedStart,
114             guaranteedEnd = store.guaranteedEnd,
115             totalCount = store.getTotalCount(),
116             numFromEdge = Math.ceil(me.percentageFromEdge * store.pageSize),
117             position = t.scrollTop,
118             visibleStart = Math.floor(position / me.rowHeight),
119             view = panel.down('tableview'),
120             viewEl = view.el,
121             visibleHeight = viewEl.getHeight(),
122             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
123             visibleEnd = visibleStart + visibleAhead,
124             prevPage = Math.floor(visibleStart / store.pageSize),
125             nextPage = Math.floor(visibleEnd / store.pageSize) + 2,
126             lastPage = Math.ceil(totalCount / store.pageSize),
127             //requestStart = visibleStart,
128             requestStart = Math.floor(visibleStart / me.snapIncrement) * me.snapIncrement,
129             requestEnd = requestStart + pageSize - 1,
130             activePrefetch = me.activePrefetch;
131
132         me.visibleStart = visibleStart;
133         me.visibleEnd = visibleEnd;
134         
135         
136         me.syncScroll = true;
137         if (totalCount >= pageSize) {
138             // end of request was past what the total is, grab from the end back a pageSize
139             if (requestEnd > totalCount - 1) {
140                 this.cancelLoad();
141                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
142                     me.syncScroll = true;
143                 }
144                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
145             // Out of range, need to reset the current data set
146             } else if (visibleStart < guaranteedStart || visibleEnd > guaranteedEnd) {
147                 if (store.rangeSatisfied(requestStart, requestEnd)) {
148                     this.cancelLoad();
149                     store.guaranteeRange(requestStart, requestEnd);
150                 } else {
151                     store.mask();
152                     me.attemptLoad(requestStart, requestEnd);
153                 }
154                 // dont sync the scroll view immediately, sync after the range has been guaranteed
155                 me.syncScroll = false;
156             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
157                 me.syncScroll = true;
158                 store.prefetchPage(prevPage);
159             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
160                 me.syncScroll = true;
161                 store.prefetchPage(nextPage);
162             }
163         }
164     
165     
166         if (me.syncScroll) {
167             me.syncTo();
168         }
169     },
170     
171     getSizeCalculation: function() {
172         // Use the direct ownerCt here rather than the scrollerOwner
173         // because we are calculating widths/heights.
174         var owner = this.ownerCt,
175             view   = owner.getView(),
176             store  = this.store,
177             dock   = this.dock,
178             elDom  = this.el.dom,
179             width  = 1,
180             height = 1;
181         
182         if (!this.rowHeight) {
183             this.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
184         }
185
186         height = store.getTotalCount() * this.rowHeight;
187
188         if (isNaN(width)) {
189             width = 1;
190         }
191         if (isNaN(height)) {
192             height = 1;
193         }
194         return {
195             width: width,
196             height: height
197         };
198     },
199     
200     attemptLoad: function(start, end) {
201         var me = this;
202         if (!me.loadTask) {
203             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
204         }
205         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
206     },
207     
208     cancelLoad: function() {
209         if (this.loadTask) {
210             this.loadTask.cancel();
211         }
212     },
213     
214     doAttemptLoad:  function(start, end) {
215         var store = this.getPanel().store;
216         store.guaranteeRange(start, end);
217     },
218     
219     setViewScrollTop: function(scrollTop, useMax) {
220         var owner = this.getPanel(),
221             items = owner.query('tableview'),
222             i = 0,
223             len = items.length,
224             center,
225             centerEl,
226             calcScrollTop,
227             maxScrollTop,
228             scrollerElDom = this.el.dom;
229             
230         owner.virtualScrollTop = scrollTop;
231             
232         center = items[1] || items[0];
233         centerEl = center.el.dom;
234         
235         maxScrollTop = ((owner.store.pageSize * this.rowHeight) - centerEl.clientHeight);
236         calcScrollTop = (scrollTop % ((owner.store.pageSize * this.rowHeight) + 1));
237         if (useMax) {
238             calcScrollTop = maxScrollTop;
239         }
240         if (calcScrollTop > maxScrollTop) {
241             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
242             return;
243             // calcScrollTop = maxScrollTop;
244         }
245         for (; i < len; i++) {
246             items[i].el.dom.scrollTop = calcScrollTop;
247         }
248     }
249 });
250