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