]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/data/PageMap.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / data / PageMap.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.data.PageMap\r
3 * @extends Ext.util.LruCache\r
4 * Private class for use by only Store when configured `buffered: true`.\r
5 * @private\r
6 */\r
7Ext.define('Ext.data.PageMap', {\r
8 extend: 'Ext.util.LruCache',\r
9\r
10 config: {\r
11 store: null,\r
12\r
13 /**\r
14 * @cfg {Number} pageSize\r
15 * The size of pages in this map.\r
16 */\r
17 pageSize: 0,\r
18\r
19 /**\r
20 * @cfg {String} rootProperty\r
21 * The root property to use for aggregation, filtering and sorting. By default\r
22 * this is `null` but when containing things like {@link Ext.data.Model records}\r
23 * this config would likely be set to "data" so that property names are applied\r
24 * to the fields of each record.\r
25 */\r
26 rootProperty: ''\r
27 },\r
28\r
29 // Maintain a generation counter, so that the Store can reject incoming pages destined for the previous generation\r
30 clear: function(initial) {\r
31 var me = this;\r
32 me.pageMapGeneration = (me.pageMapGeneration || 0) + 1;\r
33\r
34 // Map of internalId to recordIndex\r
35 me.indexMap = {};\r
36\r
37 me.callParent([initial]);\r
38 },\r
39\r
40 //<debug>\r
41 updatePageSize: function(value, oldValue) {\r
42 if (oldValue != null) {\r
43 throw "pageMap page size may not be changed";\r
44 }\r
45 },\r
46 //</debug>\r
47\r
48 forEach: function(fn, scope) {\r
49 var me = this,\r
50 pageNumbers = Ext.Object.getKeys(me.map),\r
51 pageCount = pageNumbers.length,\r
52 pageSize = me.getPageSize(),\r
53 i, j,\r
54 pageNumber,\r
55 page,\r
56 len;\r
57\r
58 for (i = 0; i < pageCount; i++) {\r
59 pageNumbers[i] = +pageNumbers[i];\r
60 }\r
61 Ext.Array.sort(pageNumbers, Ext.Array.numericSortFn);\r
62 scope = scope || me;\r
63 for (i = 0; i < pageCount; i++) {\r
64 pageNumber = pageNumbers[i];\r
65 page = me.getPage(pageNumber);\r
66 len = page.length;\r
67 for (j = 0; j < len; j++) {\r
68 if (fn.call(scope, page[j], (pageNumber - 1) * pageSize + j) === false) {\r
69 return;\r
70 }\r
71 }\r
72 }\r
73 },\r
74\r
75 /**\r
76 * Returns the first record in this page map which elicits a true return value from the\r
77 * passed selection function.\r
78 *\r
79 * **IMPORTANT\r
80 * This can ONLY find records which happen to be cached in the page cache. This will be parts of the dataset around the currently\r
81 * visible zone, or recently visited zones if the pages have not yet been purged from the cache.\r
82 * \r
83 * This CAN NOT find records which have not been loaded into the cache.**\r
84 *\r
85 * If full client side searching is required, do not use a buffered store, instead use a regular, fully loaded store and\r
86 * use the {@link Ext.grid.plugin.BufferedRenderer BufferedRenderer} plugin to minimize DOM footprint.\r
87 * @param {Function} fn The selection function to execute for each item.\r
88 * @param {Mixed} fn.rec The record.\r
89 * @param {Mixed} fn.index The index in the total dataset of the record.\r
90 * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to this PageMap.\r
91 * @return {Object} The first record in this page map which returned true from the selection\r
92 * function, or null if none was found.\r
93 */\r
94 findBy: function(fn, scope) {\r
95 var me = this,\r
96 result = null;\r
97\r
98 scope = scope || me;\r
99 me.forEach(function(rec, index) {\r
100 if (fn.call(scope, rec, index)) {\r
101 result = rec;\r
102 return false;\r
103 }\r
104 });\r
105 return result;\r
106 },\r
107\r
108 /**\r
109 * Returns the index *in the whole dataset* of the first record in this page map which elicits a true return value from the\r
110 * passed selection function.\r
111 *\r
112 * **IMPORTANT\r
113 * This can ONLY find records which happen to be cached in the page cache. This will be parts of the dataset around the currently\r
114 * visible zone, or recently visited zones if the pages have not yet been purged from the cache.\r
115 * \r
116 * This CAN NOT find records which have not been loaded into the cache.**\r
117 *\r
118 * If full client side searching is required, do not use a buffered store, instead use a regular, fully loaded store and\r
119 * use the {@link Ext.grid.plugin.BufferedRenderer BufferedRenderer} plugin to minimize DOM footprint.\r
120 * @param {Function} fn The selection function to execute for each item.\r
121 * @param {Mixed} fn.rec The record.\r
122 * @param {Mixed} fn.index The index in the total dataset of the record.\r
123 * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to this PageMap.\r
124 * @return {Number} The index first record in this page map which returned true from the selection\r
125 * function, or -1 if none was found.\r
126 */\r
127 findIndexBy: function(fn, scope) {\r
128 var me = this,\r
129 result = -1;\r
130\r
131 scope = scope || me;\r
132 me.forEach(function(rec, index) {\r
133 if (fn.call(scope, rec)) {\r
134 result = index;\r
135 return false;\r
136 }\r
137 });\r
138 return result;\r
139 },\r
140\r
141 find: function (property, value, start, startsWith, endsWith, ignoreCase) {\r
142 if (Ext.isEmpty(value, false)) {\r
143 return null;\r
144 }\r
145\r
146 var regex = Ext.String.createRegex(value, startsWith, endsWith, ignoreCase),\r
147 root = this.getRootProperty();\r
148\r
149 return this.findBy(function (item) {\r
150 return item && regex.test((root ? item[root] : item)[property]);\r
151 }, null, start);\r
152 },\r
153\r
154 findIndex: function (property, value, start, startsWith, endsWith, ignoreCase) {\r
155 if (Ext.isEmpty(value, false)) {\r
156 return null;\r
157 }\r
158\r
159 var regex = Ext.String.createRegex(value, startsWith, endsWith, ignoreCase),\r
160 root = this.getRootProperty();\r
161\r
162 return this.findIndexBy(function (item) {\r
163 return item && regex.test((root ? item[root] : item)[property]);\r
164 }, null, start);\r
165 },\r
166\r
167 getPageFromRecordIndex: function(index) {\r
168 return Math.floor(index / this.getPageSize()) + 1;\r
169 },\r
170\r
171 addAll: function(records) {\r
172 //<debug>\r
173 if (this.getCount()) {\r
174 Ext.raise('Cannot addAll to a non-empty PageMap');\r
175 }\r
176 //</debug>\r
177 this.addPage(1, records);\r
178 },\r
179\r
180 addPage: function(pageNumber, records) {\r
181 var me = this,\r
182 pageSize = me.getPageSize(),\r
183 lastPage = pageNumber + Math.floor((records.length - 1) / pageSize),\r
184 storeIndex = (pageNumber - 1) * pageSize,\r
185 indexMap = me.indexMap,\r
186 page, i, len, startIdx;\r
187\r
188 // Account for being handed a block of records spanning several pages.\r
189 // This can happen when loading from a MemoryProxy before a viewSize has been determined.\r
190 for (startIdx = 0; pageNumber <= lastPage; pageNumber++, startIdx += pageSize) {\r
191 page = Ext.Array.slice(records, startIdx, startIdx + pageSize);\r
192\r
193 // Maintain the indexMap so that we can implement indexOf(record)\r
194 for (i = 0, len = page.length; i < len; i++) {\r
195 indexMap[page[i].internalId] = storeIndex++;\r
196 }\r
197 me.add(pageNumber, page);\r
198 me.fireEvent('pageadd', me, pageNumber, page);\r
199 }\r
200 },\r
201\r
202 getCount: function() {\r
203 var result = this.callParent();\r
204 if (result) {\r
205 result = (result - 1) * this.getPageSize() + this.last.value.length;\r
206 }\r
207 return result;\r
208 },\r
209\r
210 getByInternalId: function(internalId) {\r
211 var index = this.indexMap[internalId];\r
212 if (index != null) {\r
213 return this.getAt(index);\r
214 }\r
215 },\r
216\r
217 indexOf: function(record) {\r
218 var result = -1;\r
219 if (record) {\r
220 result = this.indexMap[record.internalId];\r
221 if (result == null) {\r
222 result = -1;\r
223 }\r
224 }\r
225 return result;\r
226 },\r
227\r
228 insert: function() {\r
229 //<debug>\r
230 Ext.raise('insert operation not suppported into buffered Store');\r
231 //</debug>\r
232 },\r
233\r
234 remove: function() {\r
235 //<debug>\r
236 Ext.raise('remove operation not suppported from buffered Store');\r
237 //</debug>\r
238 },\r
239\r
240 removeAt: function() {\r
241 //<debug>\r
242 Ext.raise('removeAt operation not suppported from buffered Store');\r
243 //</debug>\r
244 },\r
245\r
246 removeAtKey: function (page) {\r
247 // Allow observers to veto\r
248 var me = this,\r
249 thePage = me.getPage(page),\r
250 len,\r
251 i,\r
252 result;\r
253\r
254 if (thePage) {\r
255 if (me.fireEvent('beforepageremove', me, page, thePage) !== false) {\r
256 len = thePage.length;\r
257 for (i = 0; i < len; i++) {\r
258 delete me.indexMap[thePage[i].internalId];\r
259 }\r
260 result = me.callParent(arguments);\r
261 me.fireEvent('pageremove', me, page, thePage);\r
262\r
263 // Empty the page array *after* informing observers that the records have exited.\r
264 thePage.length = 0;\r
265 }\r
266 }\r
267 return result;\r
268 },\r
269\r
270 getPage: function(pageNumber) {\r
271 return this.get(pageNumber);\r
272 },\r
273\r
274 hasRange: function(start, end) {\r
275 var me = this,\r
276 pageNumber = me.getPageFromRecordIndex(start),\r
277 endPageNumber = me.getPageFromRecordIndex(end);\r
278\r
279 for (; pageNumber <= endPageNumber; pageNumber++) {\r
280 if (!me.hasPage(pageNumber)) {\r
281 return false;\r
282 }\r
283 }\r
284 // Check that the last page is filled enough to encapsulate the range.\r
285 return (endPageNumber - 1) * me._pageSize + me.getPage(endPageNumber).length > end;\r
286 },\r
287\r
288 hasPage: function(pageNumber) {\r
289 // We must use this.get to trigger an access so that the page which is checked for presence is not eligible for pruning\r
290 return !!this.get(pageNumber);\r
291 },\r
292\r
293 peekPage: function(pageNumber) {\r
294 return this.map[pageNumber];\r
295 },\r
296\r
297 getAt: function(index) {\r
298 return this.getRange(index, index + 1)[0];\r
299 },\r
300\r
301 getRange: function(start, end) {\r
302 // Store's backing Collection now uses EXCLUSIVE endIndex\r
303 // So store will always pass the endIndex+1\r
304 end--;\r
305\r
306 if (!this.hasRange(start, end)) {\r
307 Ext.raise('PageMap asked for range which it does not have');\r
308 }\r
309 var me = this,\r
310 Array = Ext.Array,\r
311 pageSize = me.getPageSize(),\r
312 startPageNumber = me.getPageFromRecordIndex(start),\r
313 endPageNumber = me.getPageFromRecordIndex(end),\r
314 dataStart = (startPageNumber - 1) * pageSize,\r
315 dataEnd = (endPageNumber * pageSize) - 1,\r
316 pageNumber = startPageNumber,\r
317 result = [],\r
318 sliceBegin, sliceEnd, doSlice;\r
319\r
320 for (; pageNumber <= endPageNumber; pageNumber++) {\r
321\r
322 // First and last pages *may* need slicing to cut into the actual wanted records\r
323 if (pageNumber === startPageNumber) {\r
324 sliceBegin = start - dataStart;\r
325 doSlice = sliceBegin > 0;\r
326 } else {\r
327 sliceBegin = 0;\r
328 doSlice = false;\r
329 }\r
330 if (pageNumber === endPageNumber) {\r
331 sliceEnd = pageSize - (dataEnd - end);\r
332 doSlice = doSlice || sliceEnd < pageSize;\r
333 }\r
334\r
335 // First and last pages will need slicing\r
336 if (doSlice) {\r
337 Array.push(result, Array.slice(me.getPage(pageNumber), sliceBegin, sliceEnd));\r
338 } else {\r
339 Array.push(result, me.getPage(pageNumber));\r
340 }\r
341 }\r
342 return result;\r
343 },\r
344\r
345 destroy: function() {\r
346 this.callParent();\r
347 this.indexMap = {};\r
348\r
349 }\r
350});\r