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