]> git.proxmox.com Git - sencha-touch.git/blob - src/src/data/plugin/Buffered.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / data / plugin / Buffered.js
1 /**
2 * @class Ext.data.plugin.Buffered
3 * Description
4 */
5 Ext.define('Ext.data.plugin.Buffered', {
6 alias: 'plugin.storebuffered',
7
8 extend: 'Ext.Evented',
9
10 requires: [
11 'Ext.util.BufferedCollection'
12 ],
13
14 config: {
15 store: null,
16
17 /**
18 * @cfg {Number} trailingBufferZone
19 * When {@link #buffered}, the number of extra records to keep cached on the trailing side of scrolling buffer
20 * as scrolling proceeds. A larger number means fewer replenishments from the server.
21 */
22 trailingBufferZone: 25,
23
24 /**
25 * @cfg {Number} leadingBufferZone
26 * When {@link #buffered}, the number of extra rows to keep cached on the leading side of scrolling buffer
27 * as scrolling proceeds. A larger number means fewer replenishments from the server.
28 */
29 leadingBufferZone: 50,
30
31 /**
32 * @cfg {Number} purgePageCount
33 * *Valid only when used with a {@link Ext.data.Store#buffered buffered} Store.*
34 *
35 * The number of pages *additional to the required buffered range* to keep in the prefetch cache before purging least recently used records.
36 *
37 * For example, if the height of the view area and the configured {@link #trailingBufferZone} and {@link #leadingBufferZone} require that there
38 * are three pages in the cache, then a `purgePageCount` of 5 ensures that up to 8 pages can be in the page cache any any one time.
39 *
40 * A value of 0 indicates to never purge the prefetched data.
41 */
42 purgePageCount: 5,
43
44 // Number of records to load into a buffered grid before it has been bound to a view of known size
45 viewSize: 0,
46
47 bufferedCollection: {}
48 },
49
50 init: function(store) {
51 this.setStore(store);
52 this.pageRequests = {};
53 },
54
55 applyBufferedCollection: function(config) {
56 return Ext.factory(config, Ext.util.BufferedCollection, this.getBufferedCollection());
57 },
58
59 updateBufferedCollection: function(collection) {
60 var store = this.getStore();
61 if (store) {
62 Ext.destroy(store.data);
63 store.data = collection;
64 }
65 },
66
67 updateStore: function(store) {
68 if (store) {
69 store.setRemoteSort(true);
70 store.setRemoteFilter(true);
71 store.setRemoteGroup(true);
72 store.setPageSize(this.getViewSize());
73 this.updateBufferedCollection(this.getBufferedCollection());
74
75 //<debug>
76 Ext.Function.interceptBefore(store, 'add', function() {
77 Ext.Error.raise({
78 msg: 'add method may not be called on a buffered store'
79 });
80 });
81 //</debug>
82
83 Ext.apply(store, {
84 load: Ext.Function.bind(this.load, this),
85 buffered: this
86 });
87 }
88 },
89
90 updateViewSize: function(viewSize) {
91 var store = this.getStore();
92 if (store) {
93 store.setPageSize(viewSize);
94 this.getBufferedCollection().setPageSize(viewSize);
95 }
96 },
97
98 requestRange: function(start, end, callback, scope) {
99 if (this.isRangeCached(start, end)) {
100 callback.call(scope || this, this.getBufferedCollection().getRange(start, end));
101 } else {
102 this.loadPrefetch({
103 start: start,
104 limit: end - start,
105 callback: callback,
106 scope: scope || this
107 });
108 }
109 },
110
111 load: function(options, scope) {
112 var store = this.getStore(),
113 currentPage = store.currentPage,
114 viewSize = this.getViewSize();
115
116 options = options || {};
117
118 if (Ext.isFunction(options)) {
119 options = {
120 callback: options,
121 scope: scope || this
122 };
123 }
124
125 Ext.applyIf(options, {
126 sorters: store.getSorters(),
127 filters: store.getFilters(),
128 grouper: store.getGrouper(),
129
130 page: currentPage,
131 start: (currentPage - 1) * viewSize,
132 limit: viewSize,
133
134 addRecords: false,
135 action: 'read',
136 model: store.getModel()
137 });
138
139 this.loadPrefetch(options);
140 },
141
142 loadPrefetch: function(options) {
143 var me = this,
144 startIndex = options.start,
145 endIndex = options.start + options.limit - 1,
146 trailingBufferZone = me.getTrailingBufferZone(),
147 leadingBufferZone = me.getLeadingBufferZone(),
148 startPage = me.getPageFromRecordIndex(Math.max(startIndex - trailingBufferZone, 0)),
149 endPage = me.getPageFromRecordIndex(endIndex + leadingBufferZone),
150 bufferedCollection = me.getBufferedCollection(),
151 store = me.getStore(),
152 prefetchOptions = Ext.apply({}, options),
153 waitForRequestedRange, totalCount, i, records;
154
155 // Wait for the viewable range to be available
156 waitForRequestedRange = function() {
157 if (me.isRangeCached(startIndex, endIndex)) {
158 store.loading = false;
159
160 bufferedCollection.un('pageadded', waitForRequestedRange);
161 records = bufferedCollection.getRange(startIndex, endIndex);
162
163 if (options.callback) {
164 options.callback.call(options.scope || me, records, startIndex, endIndex, options);
165 }
166 }
167 };
168
169 delete prefetchOptions.callback;
170
171 store.on('prefetch', function(store, records, successful) {
172 if (successful) {
173 totalCount = store.getTotalCount();
174 if (totalCount) {
175 bufferedCollection.on('pageadded', waitForRequestedRange);
176
177 // As soon as we have the size of the dataset, ensure we are not waiting for more than can ever arrive,
178 endIndex = Math.min(endIndex, totalCount - 1);
179
180 // And make sure we never ask for pages beyond the end of the dataset.
181 endPage = me.getPageFromRecordIndex(Math.min(endIndex + leadingBufferZone, totalCount - 1));
182
183 for (i = startPage + 1; i <= endPage; ++i) {
184 me.prefetchPage(i, prefetchOptions);
185 }
186 }
187 }
188 }, this, {single: true});
189
190 me.prefetchPage(startPage, prefetchOptions);
191 },
192
193 /**
194 * Prefetches a page of data.
195 * @param {Number} page The page to prefetch
196 * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
197 * See {@link #method-load}
198 */
199 prefetchPage: function(page, options) {
200 var me = this,
201 viewSize = me.getViewSize();
202
203 // Copy options into a new object so as not to mutate passed in objects
204 me.prefetch(Ext.applyIf({
205 page: page,
206 start: (page - 1) * viewSize,
207 limit: viewSize
208 }, options));
209 },
210
211 /**
212 * Prefetches data into the store using its configured {@link #proxy}.
213 * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
214 * See {@link #method-load}
215 */
216 prefetch: function(options) {
217 var me = this,
218 pageSize = me.getViewSize(),
219 store = me.getStore(),
220 operation;
221
222 // Always get whole pages.
223 if (!options.page) {
224 options.page = me.getPageFromRecordIndex(options.start);
225 options.start = (options.page - 1) * pageSize;
226 options.limit = Math.ceil(options.limit / pageSize) * pageSize;
227 }
228
229 // Currently not requesting this page, then request it...
230 if (!me.pageRequests[options.page]) {
231 // Copy options into a new object so as not to mutate passed in objects
232 options = Ext.applyIf({
233 action : 'read',
234 sorters: store.getSorters(),
235 filters: store.getFilters(),
236 grouper: store.getGrouper()
237 }, options);
238
239 operation = Ext.create('Ext.data.Operation', options);
240
241 if (store.fireEvent('beforeprefetch', me, operation) !== false) {
242 me.pageRequests[options.page] = store.getProxy().read(operation, me.onProxyPrefetch, me);
243 }
244 }
245
246 return me;
247 },
248
249 /**
250 * Called after the configured proxy completes a prefetch operation.
251 * @private
252 * @param {Ext.data.Operation} operation The operation that completed
253 */
254 onProxyPrefetch: function(operation) {
255 var me = this,
256 resultSet = operation.getResultSet(),
257 records = operation.getRecords(),
258 successful = operation.wasSuccessful(),
259 store = me.getStore(),
260 bufferedCollection = me.getBufferedCollection(),
261 page = operation.getPage(),
262 total;
263
264 if (resultSet) {
265 total = resultSet.getTotal();
266
267 if (total !== store.getTotalCount()) {
268 store.setTotalCount(total);
269 bufferedCollection.setTotalCount(total);
270
271 store.fireEvent('totalcountchange', store, total);
272 }
273 }
274
275 store.loading = false;
276
277 if (successful) {
278 me.cachePage(records, page);
279 }
280
281 store.fireEvent('prefetch', store, records, successful, operation);
282
283 //this is a callback that would have been passed to the 'read' function and is optional
284 Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
285 },
286
287 /**
288 * Caches the records in the prefetch and stripes them with their server-side
289 * index.
290 * @private
291 * @param {Ext.data.Model[]} records The records to cache
292 * @param {Ext.data.Operation} page The associated operation
293 */
294 cachePage: function(records, page) {
295 var me = this,
296 bufferedCollection = me.getBufferedCollection(),
297 ln = records.length, i;
298
299 // Add the fetched page into the pageCache
300 for (i = 0; i < ln; i++) {
301 records[i].join(me);
302 }
303
304 bufferedCollection.addPage(page, records);
305 },
306
307 /**
308 * Determines the page from a record index
309 * @param {Number} index The record index
310 * @return {Number} The page the record belongs to
311 */
312 getPageFromRecordIndex: function(index) {
313 return Math.floor(index / this.getViewSize()) + 1;
314 },
315
316 /**
317 * Determines if the passed range is available in the page cache.
318 * @private
319 * @param {Number} start The start index
320 * @param {Number} end The end index in the range
321 */
322 isRangeCached: function(start, end) {
323 return this.getBufferedCollection().hasRange(start, end);
324 }
325 });