]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * List is a custom styled DataView which allows Grouping, Indexing, Icons, and a Disclosure.\r | |
3 | *\r | |
4 | * @example miniphone preview\r | |
5 | * Ext.create('Ext.List', {\r | |
6 | * fullscreen: true,\r | |
7 | * itemTpl: '{title}',\r | |
8 | * data: [\r | |
9 | * { title: 'Item 1' },\r | |
10 | * { title: 'Item 2' },\r | |
11 | * { title: 'Item 3' },\r | |
12 | * { title: 'Item 4' }\r | |
13 | * ]\r | |
14 | * });\r | |
15 | *\r | |
16 | * A more advanced example showing a list of people grouped by last name:\r | |
17 | *\r | |
18 | * @example miniphone preview\r | |
19 | * Ext.define('Contact', {\r | |
20 | * extend: 'Ext.data.Model',\r | |
21 | * config: {\r | |
22 | * fields: ['firstName', 'lastName']\r | |
23 | * }\r | |
24 | * });\r | |
25 | *\r | |
26 | * var store = Ext.create('Ext.data.Store', {\r | |
27 | * model: 'Contact',\r | |
28 | * sorters: 'lastName',\r | |
29 | *\r | |
30 | * grouper: {\r | |
31 | * groupFn: function(record) {\r | |
32 | * return record.get('lastName')[0];\r | |
33 | * }\r | |
34 | * },\r | |
35 | *\r | |
36 | * data: [\r | |
37 | * { firstName: 'Peter', lastName: 'Venkman' },\r | |
38 | * { firstName: 'Raymond', lastName: 'Stantz' },\r | |
39 | * { firstName: 'Egon', lastName: 'Spengler' },\r | |
40 | * { firstName: 'Winston', lastName: 'Zeddemore'}\r | |
41 | * ]\r | |
42 | * });\r | |
43 | *\r | |
44 | * Ext.create('Ext.List', {\r | |
45 | * fullscreen: true,\r | |
46 | * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',\r | |
47 | * store: store,\r | |
48 | * grouped: true\r | |
49 | * });\r | |
50 | *\r | |
51 | * If you want to dock items to the bottom or top of a List, you can use the scrollDock configuration on child items in this List. The following example adds a button to the bottom of the List.\r | |
52 | *\r | |
53 | * @example phone preview\r | |
54 | * Ext.define('Contact', {\r | |
55 | * extend: 'Ext.data.Model',\r | |
56 | * config: {\r | |
57 | * fields: ['firstName', 'lastName']\r | |
58 | * }\r | |
59 | * });\r | |
60 | *\r | |
61 | * var store = Ext.create('Ext.data.Store', {\r | |
62 | * model: 'Contact',\r | |
63 | * sorters: 'lastName',\r | |
64 | *\r | |
65 | * grouper: {\r | |
66 | * groupFn: function(record) {\r | |
67 | * return record.get('lastName')[0];\r | |
68 | * }\r | |
69 | * },\r | |
70 | *\r | |
71 | * data: [\r | |
72 | * { firstName: 'Peter', lastName: 'Venkman' },\r | |
73 | * { firstName: 'Raymond', lastName: 'Stantz' },\r | |
74 | * { firstName: 'Egon', lastName: 'Spengler' },\r | |
75 | * { firstName: 'Winston', lastName: 'Zeddemore'}\r | |
76 | * ]\r | |
77 | * });\r | |
78 | *\r | |
79 | * Ext.create('Ext.List', {\r | |
80 | * fullscreen: true,\r | |
81 | * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',\r | |
82 | * store: store,\r | |
83 | * items: [{\r | |
84 | * xtype: 'button',\r | |
85 | * scrollDock: 'bottom',\r | |
86 | * docked: 'bottom',\r | |
87 | * text: 'Load More...'\r | |
88 | * }]\r | |
89 | * });\r | |
90 | */\r | |
91 | Ext.define('Ext.dataview.List', {\r | |
92 | alternateClassName: 'Ext.List',\r | |
93 | extend: 'Ext.dataview.DataView',\r | |
94 | xtype: 'list',\r | |
95 | \r | |
96 | mixins: ['Ext.mixin.Hookable'],\r | |
97 | \r | |
98 | requires: [\r | |
99 | 'Ext.data.Store',\r | |
100 | 'Ext.dataview.IndexBar',\r | |
101 | 'Ext.dataview.ListItemHeader',\r | |
102 | 'Ext.dataview.component.ListItem',\r | |
103 | 'Ext.dataview.component.SimpleListItem',\r | |
104 | 'Ext.util.PositionMap'\r | |
105 | ],\r | |
106 | \r | |
107 | /**\r | |
108 | * @event disclose\r | |
109 | * @preventable\r | |
110 | * Fires whenever a disclosure is handled\r | |
111 | * @param {Ext.dataview.List} this The List instance\r | |
112 | * @param {Ext.data.Model} record The record associated to the item\r | |
113 | * @param {HTMLElement} target The element disclosed\r | |
114 | * @param {Number} index The index of the item disclosed\r | |
115 | * @param {Ext.EventObject} e The event object\r | |
116 | */\r | |
117 | \r | |
118 | config: {\r | |
119 | /**\r | |
120 | * @cfg {Object} container\r | |
121 | * This config is used to control the internal {@link Ext.Container} created to\r | |
122 | * manage this list's items. One common use for this is to apply a {@link #userCls}\r | |
123 | * to the item container.\r | |
124 | *\r | |
125 | * {\r | |
126 | * xtype: 'list',\r | |
127 | * container: {\r | |
128 | * userCls: 'mylist-cls'\r | |
129 | * },\r | |
130 | * ...\r | |
131 | * }\r | |
132 | *\r | |
133 | * @since 6.0.1\r | |
134 | */\r | |
135 | container: {\r | |
136 | lazy: true,\r | |
137 | $value: {\r | |
138 | xtype: 'container',\r | |
139 | scrollable: {}\r | |
140 | }\r | |
141 | },\r | |
142 | \r | |
143 | /**\r | |
144 | * @cfg layout\r | |
145 | * Hide layout config in DataView. It only causes confusion.\r | |
146 | * @accessor\r | |
147 | * @private\r | |
148 | */\r | |
149 | layout: 'fit',\r | |
150 | \r | |
151 | /**\r | |
152 | * @cfg {Boolean/Object} indexBar\r | |
153 | * `true` to render an alphabet IndexBar docked on the right.\r | |
154 | * This can also be a config object that will be passed to {@link Ext.IndexBar}.\r | |
155 | * @accessor\r | |
156 | */\r | |
157 | indexBar: false,\r | |
158 | \r | |
159 | icon: null,\r | |
160 | \r | |
161 | /**\r | |
162 | * @cfg {Boolean} preventSelectionOnDisclose `true` to prevent the item selection when the user\r | |
163 | * taps a disclose icon.\r | |
164 | * @accessor\r | |
165 | */\r | |
166 | preventSelectionOnDisclose: true,\r | |
167 | \r | |
168 | /**\r | |
169 | * @cfg baseCls\r | |
170 | * @inheritdoc\r | |
171 | */\r | |
172 | baseCls: Ext.baseCSSPrefix + 'list',\r | |
173 | \r | |
174 | /**\r | |
175 | * @cfg {Boolean} pinHeaders\r | |
176 | * Whether or not to pin headers on top of item groups while scrolling for an iPhone native list experience.\r | |
177 | * @accessor\r | |
178 | */\r | |
179 | pinHeaders: true,\r | |
180 | \r | |
181 | /**\r | |
182 | * @cfg {Boolean} grouped\r | |
183 | * Whether or not to group items in the provided Store with a header for each item.\r | |
184 | * @accessor\r | |
185 | */\r | |
186 | grouped: null,\r | |
187 | \r | |
188 | /**\r | |
189 | * @cfg {Boolean/Function/Object} onItemDisclosure\r | |
190 | * `true` to display a disclosure icon on each list item.\r | |
191 | * The list will still fire the disclose event, and the event can be stopped before itemtap.\r | |
192 | * By setting this config to a function, the function passed will be called when the disclosure\r | |
193 | * is tapped.\r | |
194 | * Finally you can specify an object with a 'scope' and 'handler'\r | |
195 | * property defined. This will also be bound to the tap event listener\r | |
196 | * and is useful when you want to change the scope of the handler.\r | |
197 | * @accessor\r | |
198 | */\r | |
199 | onItemDisclosure: null,\r | |
200 | \r | |
201 | /**\r | |
202 | * @cfg {String} disclosureProperty\r | |
203 | * A property to check on each record to display the disclosure on a per record basis. This\r | |
204 | * property must be false to prevent the disclosure from being displayed on the item.\r | |
205 | * @accessor\r | |
206 | */\r | |
207 | disclosureProperty: 'disclosure',\r | |
208 | \r | |
209 | /**\r | |
210 | * @cfg {Boolean} useComponents\r | |
211 | * Flag the use a component based DataView implementation. This allows the full use of components in the\r | |
212 | * DataView at the cost of some performance.\r | |
213 | *\r | |
214 | * @accessor\r | |
215 | * @private\r | |
216 | */\r | |
217 | \r | |
218 | /**\r | |
219 | * @cfg {Object} itemConfig\r | |
220 | * A configuration object that is passed to every item created by a component based DataView. Because each\r | |
221 | * item that a List renders is a Component, we can pass configuration options to each component to\r | |
222 | * easily customize how each child component behaves.\r | |
223 | * @accessor\r | |
224 | * @private\r | |
225 | */\r | |
226 | \r | |
227 | /**\r | |
228 | * @cfg {Number} maxItemCache\r | |
229 | * Maintains a cache of reusable components when using a component based DataView. Improving performance at\r | |
230 | * the cost of memory.\r | |
231 | * Note this is currently only used when useComponents is true.\r | |
232 | * @accessor\r | |
233 | * @private\r | |
234 | */\r | |
235 | \r | |
236 | /**\r | |
237 | * @cfg {String} defaultType\r | |
238 | * The xtype used for the component based DataView. Defaults to dataitem.\r | |
239 | * Note this is only used when useComponents is true.\r | |
240 | * @accessor\r | |
241 | */\r | |
242 | defaultType: undefined,\r | |
243 | \r | |
244 | /**\r | |
245 | * @cfg {Object} itemMap\r | |
246 | * @private\r | |
247 | */\r | |
248 | itemMap: {},\r | |
249 | \r | |
250 | /**\r | |
251 | * @cfg {Number} itemHeight\r | |
252 | * This allows you to set the default item height and is used to roughly calculate the amount\r | |
253 | * of items needed to fill the list. By default items are around 50px high.\r | |
254 | */\r | |
255 | itemHeight: null,\r | |
256 | \r | |
257 | /**\r | |
258 | * @cfg {Boolean} variableHeights\r | |
259 | * This configuration allows you optimize the list by not having it read the DOM heights of list items.\r | |
260 | * Instead it will assume (and set) the height to be the {@link #itemHeight}.\r | |
261 | * @private\r | |
262 | */\r | |
263 | variableHeights: false,\r | |
264 | \r | |
265 | /**\r | |
266 | * @cfg {Boolean} refreshHeightOnUpdate\r | |
267 | * Set this to false if you make many updates to your list (like in an interval), but updates\r | |
268 | * won't affect the item's height. Doing this will increase the performance of these updates.\r | |
269 | */\r | |
270 | refreshHeightOnUpdate: true,\r | |
271 | \r | |
272 | /**\r | |
273 | * @cfg {Boolean} infinite\r | |
274 | * Set this to false to render all items in this list, and render them relatively.\r | |
275 | * Note that this configuration can not be dynamically changed after the list has instantiated.\r | |
276 | */\r | |
277 | infinite: false,\r | |
278 | \r | |
279 | /**\r | |
280 | * @cfg {Boolean} useSimpleItems\r | |
281 | * Set this to true if you just want to have the list create simple items that use the itemTpl.\r | |
282 | * These simple items still support headers, grouping and disclosure functionality but avoid\r | |
283 | * container layouts and deeply nested markup. For many Lists using this configuration will\r | |
284 | * drastically increase the scrolling and render performance.\r | |
285 | */\r | |
286 | useSimpleItems: true,\r | |
287 | \r | |
288 | /**\r | |
289 | * @cfg {Object} scrollable\r | |
290 | * @private\r | |
291 | */\r | |
292 | scrollable: null,\r | |
293 | \r | |
294 | /**\r | |
295 | * @cfg {Number} bufferSize\r | |
296 | * The amount of items we render additionally besides the ones currently visible.\r | |
297 | * We try to prevent the rendering of items while scrolling until the next time you stop scrolling.\r | |
298 | * If you scroll close to the end of the buffer, we start rendering individual items to always\r | |
299 | * have the {@link #minimumBufferSize} prepared.\r | |
300 | */\r | |
301 | bufferSize: 20,\r | |
302 | \r | |
303 | minimumBufferDistance: 5,\r | |
304 | \r | |
305 | /**\r | |
306 | * @cfg {Boolean} striped\r | |
307 | * Set this to true if you want the items in the list to be zebra striped, alternating their\r | |
308 | * background color.\r | |
309 | */\r | |
310 | striped: false\r | |
311 | },\r | |
312 | \r | |
313 | topRenderedIndex: 0,\r | |
314 | topVisibleIndex: 0,\r | |
315 | visibleCount: 0,\r | |
316 | \r | |
317 | //<debug>\r | |
318 | constructor: function(config) {\r | |
319 | this.callParent([config]);\r | |
320 | var layout = this.getLayout();\r | |
321 | if (layout && !layout.isFit) {\r | |
322 | Ext.Logger.error('The base layout for a DataView must always be a Fit Layout');\r | |
323 | }\r | |
324 | },\r | |
325 | //</debug>\r | |
326 | \r | |
327 | // We create complex instance arrays and objects in beforeInitialize so that we can use these inside of the initConfig process.\r | |
328 | beforeInitialize: function() {\r | |
329 | var me = this,\r | |
330 | container = me.container,\r | |
331 | baseCls = me.getBaseCls(),\r | |
332 | scrollViewElement, pinnedHeader;\r | |
333 | \r | |
334 | Ext.apply(me, {\r | |
335 | listItems: [],\r | |
336 | headerItems: [],\r | |
337 | updatedItems: [],\r | |
338 | headerMap: [],\r | |
339 | recordMap: {},\r | |
340 | scrollDockItems: {\r | |
341 | top: [],\r | |
342 | bottom: []\r | |
343 | }\r | |
344 | });\r | |
345 | \r | |
346 | me.translationMethod = 'csstransform';\r | |
347 | \r | |
348 | // Create the inner container that will actually hold all the list items\r | |
349 | if (!container) {\r | |
350 | container = me.container = me.createContainer();\r | |
351 | }\r | |
352 | \r | |
353 | // We add the container after creating it manually because when you add the container,\r | |
354 | // the items config is initialized. When this happens, any scrollDock items will be added,\r | |
355 | // which in turn tries to add these items to the container\r | |
356 | me.add(container);\r | |
357 | \r | |
358 | // We make this List's scrollable the inner containers scrollable\r | |
359 | scrollViewElement = me.scrollViewElement = container.bodyElement;\r | |
360 | me.scrollElement = container.innerElement;\r | |
361 | \r | |
362 | // Create the pinnedHeader instance thats being used when grouping is enabled\r | |
363 | // and insert it into the scrollElement\r | |
364 | pinnedHeader = me.pinnedHeader = Ext.factory({\r | |
365 | xtype: 'listitemheader',\r | |
366 | html: ' ',\r | |
367 | translatable: {\r | |
368 | translationMethod: me.translationMethod\r | |
369 | },\r | |
370 | cls: [baseCls + '-header', baseCls + '-header-swap']\r | |
371 | });\r | |
372 | pinnedHeader.translate(0, -10000);\r | |
373 | pinnedHeader.$position = -10000;\r | |
374 | scrollViewElement.insertFirst(pinnedHeader.renderElement);\r | |
375 | \r | |
376 | container.getScrollable().on({\r | |
377 | scroll: 'onScroll',\r | |
378 | refresh: 'onScrollerRefresh',\r | |
379 | scope: me\r | |
380 | });\r | |
381 | },\r | |
382 | \r | |
383 | /**\r | |
384 | * @private\r | |
385 | */\r | |
386 | createContainer: function () {\r | |
387 | var config = Ext.merge({\r | |
388 | scrollable: {\r | |
389 | autoRefresh: this.getInfinite() ? null : true\r | |
390 | }\r | |
391 | }, this.getContainer());\r | |
392 | \r | |
393 | return Ext.create(config);\r | |
394 | },\r | |
395 | \r | |
396 | getScrollable: function() {\r | |
397 | return this.container.getScrollable();\r | |
398 | },\r | |
399 | \r | |
400 | // We override DataView's initialize method with an empty function\r | |
401 | initialize: function() {\r | |
402 | var me = this,\r | |
403 | container = me.container,\r | |
404 | scrollViewElement = me.scrollViewElement,\r | |
405 | indexBar = me.getIndexBar(),\r | |
406 | triggerEvent = me.getTriggerEvent(),\r | |
407 | triggerCtEvent = me.getTriggerCtEvent();\r | |
408 | \r | |
409 | if (indexBar) {\r | |
410 | scrollViewElement.appendChild(indexBar.renderElement);\r | |
411 | }\r | |
412 | \r | |
413 | if (triggerEvent) {\r | |
414 | me.on(triggerEvent, me.onItemTrigger, me);\r | |
415 | }\r | |
416 | if (triggerCtEvent) {\r | |
417 | me.on(triggerCtEvent, me.onContainerTrigger, me);\r | |
418 | }\r | |
419 | \r | |
420 | container.element.on({\r | |
421 | delegate: '.' + me.getBaseCls() + '-disclosure',\r | |
422 | tap: 'handleItemDisclosure',\r | |
423 | scope: me\r | |
424 | });\r | |
425 | \r | |
426 | container.element.on({\r | |
427 | resize: 'onContainerResize',\r | |
428 | scope: me\r | |
429 | });\r | |
430 | \r | |
431 | // Android 2.x not a direct child\r | |
432 | container.innerElement.on({\r | |
433 | touchstart: 'onItemTouchStart',\r | |
434 | touchend: 'onItemTouchEnd',\r | |
435 | tap: 'onItemTap',\r | |
436 | taphold: 'onItemTapHold',\r | |
437 | singletap: 'onItemSingleTap',\r | |
438 | doubletap: 'onItemDoubleTap',\r | |
439 | swipe: 'onItemSwipe',\r | |
440 | delegate: '.' + Ext.baseCSSPrefix + 'list-item',\r | |
441 | scope: me\r | |
442 | });\r | |
443 | \r | |
444 | if (me.getStore()) {\r | |
445 | if (me.isPainted()) {\r | |
446 | me.refresh();\r | |
447 | } else {\r | |
448 | me.on({\r | |
449 | painted: 'refresh',\r | |
450 | single: true\r | |
451 | });\r | |
452 | }\r | |
453 | }\r | |
454 | },\r | |
455 | \r | |
456 | getRefItems: function(deep) {\r | |
457 | var result = [],\r | |
458 | candidates = this.callParent([deep]),\r | |
459 | len = candidates.length,\r | |
460 | i, candidate;\r | |
461 | \r | |
462 | // We must only return list items that are part of the rendered block.\r | |
463 | // Check that their position is not -10000\r | |
464 | for (i = 0; i < len; i++) {\r | |
465 | candidate = candidates[i];\r | |
466 | if (!candidate.hasOwnProperty('$position') || candidate.$position > -1) {\r | |
467 | result[result.length] = candidate;\r | |
468 | }\r | |
469 | }\r | |
470 | \r | |
471 | return result;\r | |
472 | },\r | |
473 | \r | |
474 | onScroll: function(scroller, x, y) {\r | |
475 | var me = this,\r | |
476 | pinnedHeader = me.pinnedHeader,\r | |
477 | store = me.getStore(),\r | |
478 | storeCount = store && store.getCount(),\r | |
479 | grouped = me.isGrouping(),\r | |
480 | infinite = me.getInfinite();\r | |
481 | \r | |
482 | // This method was originally written as an interceptor for doTranslate, so the\r | |
483 | // coordinates are expected to be in CSS translation form (negative number ==\r | |
484 | // positive scroll position). It was changed to be a scroll event listener in v5\r | |
485 | // which uses scroll position coordinates.\r | |
486 | if (x) {\r | |
487 | x = -x;\r | |
488 | }\r | |
489 | if (y) {\r | |
490 | y = -y;\r | |
491 | }\r | |
492 | \r | |
493 | if (!storeCount) {\r | |
494 | me.showEmptyText();\r | |
495 | me.showEmptyScrollDock();\r | |
496 | \r | |
497 | pinnedHeader.$position = -10000;\r | |
498 | pinnedHeader.translate(0, -10000);\r | |
499 | }\r | |
500 | else if (infinite && me.itemsCount) {\r | |
501 | me.handleItemUpdates(y);\r | |
502 | me.handleItemHeights();\r | |
503 | me.handleItemTransforms();\r | |
504 | \r | |
505 | if (!me.onIdleBound) {\r | |
506 | Ext.AnimationQueue.onIdle(me.onAnimationIdle, me);\r | |
507 | me.onIdleBound = true;\r | |
508 | }\r | |
509 | }\r | |
510 | \r | |
511 | if (grouped && me.groups && me.groups.length && me.getPinHeaders()) {\r | |
512 | me.handlePinnedHeader(y);\r | |
513 | }\r | |
514 | \r | |
515 | // This is a template method that can be intercepted by plugins to do things when scrolling\r | |
516 | me.onScrollBinder(x, y);\r | |
517 | },\r | |
518 | \r | |
519 | onScrollerRefresh: function(scroller) {\r | |
520 | var position = scroller.getPosition();\r | |
521 | this.onScroll(scroller, position.x, position.y);\r | |
522 | },\r | |
523 | \r | |
524 | onScrollBinder: function(){},\r | |
525 | \r | |
526 | handleItemUpdates: function(y) {\r | |
527 | var me = this,\r | |
528 | listItems = me.listItems,\r | |
529 | itemsCount = listItems.length,\r | |
530 | info = me.getListItemInfo(),\r | |
531 | itemMap = me.getItemMap(),\r | |
532 | bufferSize = me.getBufferSize(),\r | |
533 | lastIndex = me.getStore().getCount() - 1,\r | |
534 | minimumBufferDistance = me.getMinimumBufferDistance(),\r | |
535 | currentTopVisibleIndex = me.topVisibleIndex,\r | |
536 | topRenderedIndex = me.topRenderedIndex,\r | |
537 | updateCount, i, item, topVisibleIndex, bufferDistance, itemIndex;\r | |
538 | \r | |
539 | // This is the index of the item that is currently visible at the top\r | |
540 | me.topVisibleIndex = topVisibleIndex = Math.max(0, itemMap.findIndex(-y) || 0);\r | |
541 | \r | |
542 | if (currentTopVisibleIndex !== topVisibleIndex) {\r | |
543 | // When we are scrolling up\r | |
544 | if (currentTopVisibleIndex > topVisibleIndex) {\r | |
545 | bufferDistance = topVisibleIndex - topRenderedIndex;\r | |
546 | if (bufferDistance < minimumBufferDistance) {\r | |
547 | updateCount = Math.min(itemsCount, minimumBufferDistance - bufferDistance);\r | |
548 | \r | |
549 | if (updateCount == itemsCount) {\r | |
550 | me.topRenderedIndex = topRenderedIndex = Math.max(0, topVisibleIndex - (bufferSize - minimumBufferDistance));\r | |
551 | // Update all\r | |
552 | for (i = 0; i < updateCount; i++) {\r | |
553 | itemIndex = topRenderedIndex + i;\r | |
554 | item = listItems[i];\r | |
555 | me.updateListItem(item, itemIndex, info);\r | |
556 | }\r | |
557 | }\r | |
558 | else {\r | |
559 | for (i = 0; i < updateCount; i++) {\r | |
560 | itemIndex = topRenderedIndex - i - 1;\r | |
561 | if (itemIndex < 0) {\r | |
562 | break;\r | |
563 | }\r | |
564 | \r | |
565 | item = listItems.pop();\r | |
566 | listItems.unshift(item);\r | |
567 | me.updateListItem(item, itemIndex, info);\r | |
568 | me.topRenderedIndex--;\r | |
569 | }\r | |
570 | }\r | |
571 | }\r | |
572 | }\r | |
573 | // When we are scrolling down\r | |
574 | else {\r | |
575 | bufferDistance = bufferSize - (topVisibleIndex - topRenderedIndex);\r | |
576 | \r | |
577 | if (bufferDistance < minimumBufferDistance) {\r | |
578 | updateCount = Math.min(itemsCount, minimumBufferDistance - bufferDistance);\r | |
579 | \r | |
580 | if (updateCount == itemsCount) {\r | |
581 | me.topRenderedIndex = topRenderedIndex = Math.min(lastIndex - itemsCount, topVisibleIndex - minimumBufferDistance);\r | |
582 | // Update all\r | |
583 | for (i = 0; i < updateCount; i++) {\r | |
584 | itemIndex = topRenderedIndex + i;\r | |
585 | item = listItems[i];\r | |
586 | me.updateListItem(item, itemIndex, info);\r | |
587 | }\r | |
588 | }\r | |
589 | else {\r | |
590 | for (i = 0; i < updateCount; i++) {\r | |
591 | itemIndex = topRenderedIndex + itemsCount + i;\r | |
592 | if (itemIndex > lastIndex) {\r | |
593 | break;\r | |
594 | }\r | |
595 | \r | |
596 | item = listItems.shift();\r | |
597 | listItems.push(item);\r | |
598 | me.updateListItem(item, itemIndex, info);\r | |
599 | me.topRenderedIndex++;\r | |
600 | }\r | |
601 | }\r | |
602 | }\r | |
603 | }\r | |
604 | }\r | |
605 | },\r | |
606 | \r | |
607 | onAnimationIdle: function() {\r | |
608 | var me = this,\r | |
609 | info = me.getListItemInfo(),\r | |
610 | bufferSize = me.getBufferSize(),\r | |
611 | topVisibleIndex = me.topVisibleIndex,\r | |
612 | topRenderedIndex = me.topRenderedIndex,\r | |
613 | lastIndex = me.getStore().getCount() - 1,\r | |
614 | listItems = me.listItems,\r | |
615 | itemsCount = listItems.length,\r | |
616 | topBufferDistance, bottomBufferDistance,\r | |
617 | i, ln, item, itemIndex;\r | |
618 | \r | |
619 | topBufferDistance = topVisibleIndex - topRenderedIndex;\r | |
620 | bottomBufferDistance = topRenderedIndex + bufferSize - topVisibleIndex;\r | |
621 | \r | |
622 | if (topBufferDistance < bottomBufferDistance) {\r | |
623 | // This means there are more items below the visible list. The user\r | |
624 | // has probably just scrolled up. In this case we move some items\r | |
625 | // from the bottom to the top only if the list is scrolled down a bit\r | |
626 | if (topVisibleIndex > 0) {\r | |
627 | ln = bottomBufferDistance - topBufferDistance;\r | |
628 | \r | |
629 | for (i = 0; i < ln; i++) {\r | |
630 | itemIndex = topRenderedIndex - i - 1;\r | |
631 | if (itemIndex < 0) {\r | |
632 | break;\r | |
633 | }\r | |
634 | \r | |
635 | item = listItems.pop();\r | |
636 | listItems.unshift(item);\r | |
637 | me.updateListItem(item, itemIndex, info);\r | |
638 | me.topRenderedIndex--;\r | |
639 | }\r | |
640 | }\r | |
641 | }\r | |
642 | else {\r | |
643 | ln = topBufferDistance - bottomBufferDistance;\r | |
644 | for (i = 0; i < ln; i++) {\r | |
645 | itemIndex = topRenderedIndex + itemsCount + i;\r | |
646 | if (itemIndex > lastIndex) {\r | |
647 | break;\r | |
648 | }\r | |
649 | \r | |
650 | item = listItems.shift();\r | |
651 | listItems.push(item);\r | |
652 | me.updateListItem(item, itemIndex, info);\r | |
653 | me.topRenderedIndex++;\r | |
654 | }\r | |
655 | }\r | |
656 | \r | |
657 | me.handleItemHeights();\r | |
658 | me.handleItemTransforms();\r | |
659 | \r | |
660 | me.onIdleBound = false;\r | |
661 | },\r | |
662 | \r | |
663 | handleItemHeights: function() {\r | |
664 | var me = this,\r | |
665 | updatedItems = me.updatedItems,\r | |
666 | ln = updatedItems.length,\r | |
667 | itemMap = me.getItemMap(),\r | |
668 | useSimpleItems = me.getUseSimpleItems(),\r | |
669 | minimumHeight = itemMap.getMinimumHeight(),\r | |
670 | headerIndices = me.headerIndices,\r | |
671 | headerMap = me.headerMap,\r | |
672 | variableHeights = me.getVariableHeights(),\r | |
673 | itemIndex, i, j, jln, item, height, scrollDockHeight;\r | |
674 | \r | |
675 | for (i = 0; i < ln; i++) {\r | |
676 | item = updatedItems[i];\r | |
677 | itemIndex = item.$dataIndex;\r | |
678 | \r | |
679 | // itemIndex may not be set yet if the store is still being loaded\r | |
680 | if (itemIndex !== null) {\r | |
681 | if (variableHeights) {\r | |
682 | height = useSimpleItems ? item.element.getHeight() : item.element.getFirstChild().getHeight();\r | |
683 | height = Math.max(height, minimumHeight);\r | |
684 | } else {\r | |
685 | height = minimumHeight;\r | |
686 | }\r | |
687 | \r | |
688 | item.$ownItemHeight = height;\r | |
689 | \r | |
690 | jln = me.scrollDockItems.top.length;\r | |
691 | if (item.isFirst) {\r | |
692 | me.totalScrollDockTopHeight = 0;\r | |
693 | for (j = 0; j < jln; j++) {\r | |
694 | scrollDockHeight = me.scrollDockItems.top[j].$scrollDockHeight;\r | |
695 | height += scrollDockHeight;\r | |
696 | me.totalScrollDockTopHeight += scrollDockHeight;\r | |
697 | }\r | |
698 | }\r | |
699 | \r | |
700 | jln = me.scrollDockItems.bottom.length;\r | |
701 | if (item.isLast) {\r | |
702 | for (j = 0; j < jln; j++) {\r | |
703 | scrollDockHeight = me.scrollDockItems.bottom[j].$scrollDockHeight;\r | |
704 | height += scrollDockHeight;\r | |
705 | }\r | |
706 | }\r | |
707 | \r | |
708 | if (headerIndices && headerIndices[itemIndex]) {\r | |
709 | height += me.headerHeight;\r | |
710 | }\r | |
711 | \r | |
712 | itemMap.setItemHeight(itemIndex, height);\r | |
713 | item.$height = height;\r | |
714 | }\r | |
715 | }\r | |
716 | \r | |
717 | itemMap.update();\r | |
718 | \r | |
719 | headerMap.length = 0;\r | |
720 | for (i in headerIndices) {\r | |
721 | if (headerIndices.hasOwnProperty(i)) {\r | |
722 | headerMap.push(itemMap.map[i]);\r | |
723 | }\r | |
724 | }\r | |
725 | \r | |
726 | me.updatedItems.length = 0;\r | |
727 | \r | |
728 | me.refreshScroller(true);\r | |
729 | },\r | |
730 | \r | |
731 | handleItemTransforms: function() {\r | |
732 | var me = this,\r | |
733 | listItems = me.listItems,\r | |
734 | itemsCount = listItems.length,\r | |
735 | itemMap = me.getItemMap(),\r | |
736 | scrollDockItems = me.scrollDockItems,\r | |
737 | grouped = me.isGrouping(),\r | |
738 | item, transY, i, jln, j;\r | |
739 | \r | |
740 | for (i = 0; i < itemsCount; i++) {\r | |
741 | item = listItems[i];\r | |
742 | transY = itemMap.map[item.$dataIndex];\r | |
743 | \r | |
744 | if (!item.$hidden && item.$position !== transY) {\r | |
745 | item.$position = transY;\r | |
746 | \r | |
747 | jln = scrollDockItems.top.length;\r | |
748 | if (item.isFirst && jln) {\r | |
749 | for (j = 0; j < jln; j++) {\r | |
750 | scrollDockItems.top[j].translate(0, transY);\r | |
751 | transY += scrollDockItems.top[j].$scrollDockHeight;\r | |
752 | }\r | |
753 | }\r | |
754 | \r | |
755 | if (grouped && me.headerIndices && me.headerIndices[item.$dataIndex]) {\r | |
756 | item.getHeader().translate(0, transY);\r | |
757 | transY += me.headerHeight;\r | |
758 | }\r | |
759 | \r | |
760 | item.translate(0, transY);\r | |
761 | transY += item.$ownItemHeight;\r | |
762 | \r | |
763 | jln = scrollDockItems.bottom.length;\r | |
764 | if (item.isLast && jln) {\r | |
765 | for (j = 0; j < jln; j++) {\r | |
766 | scrollDockItems.bottom[j].translate(0, transY);\r | |
767 | transY += scrollDockItems.bottom[j].$scrollDockHeight;\r | |
768 | }\r | |
769 | }\r | |
770 | }\r | |
771 | }\r | |
772 | },\r | |
773 | \r | |
774 | handlePinnedHeader: function(y) {\r | |
775 | var me = this,\r | |
776 | pinnedHeader = me.pinnedHeader,\r | |
777 | itemMap = me.getItemMap(),\r | |
778 | groups = me.groups,\r | |
779 | headerMap = me.headerMap,\r | |
780 | headerHeight = me.headerHeight,\r | |
781 | store = me.getStore(),\r | |
782 | totalScrollDockTopHeight = me.totalScrollDockTopHeight,\r | |
783 | record, closestHeader, pushedHeader, transY, headerString;\r | |
784 | \r | |
785 | closestHeader = itemMap.binarySearch(headerMap, -y);\r | |
786 | record = groups.getAt(closestHeader).getAt(0);\r | |
787 | \r | |
788 | if (record) {\r | |
789 | pushedHeader = y + headerMap[closestHeader + 1] - headerHeight;\r | |
790 | // Top of the list or above (hide the floating header offscreen)\r | |
791 | if (y >= 0 || (closestHeader === 0 && totalScrollDockTopHeight + y >= 0) || (closestHeader === 0 && -y <= headerMap[closestHeader])) {\r | |
792 | transY = -10000;\r | |
793 | }\r | |
794 | // Scroll the floating header a bit\r | |
795 | else if (pushedHeader < 0) {\r | |
796 | transY = pushedHeader;\r | |
797 | }\r | |
798 | // Stick to the top of the screen\r | |
799 | else {\r | |
800 | transY = Math.max(0, y);\r | |
801 | }\r | |
802 | \r | |
803 | headerString = store.getGrouper().getGroupString(record);\r | |
804 | \r | |
805 | if (pinnedHeader.$currentHeader != headerString) {\r | |
806 | pinnedHeader.setHtml(headerString);\r | |
807 | pinnedHeader.$currentHeader = headerString;\r | |
808 | }\r | |
809 | \r | |
810 | if (pinnedHeader.$position != transY) {\r | |
811 | pinnedHeader.translate(0, transY);\r | |
812 | pinnedHeader.$position = transY;\r | |
813 | }\r | |
814 | }\r | |
815 | },\r | |
816 | \r | |
817 | createItem: function(config) {\r | |
818 | var me = this,\r | |
819 | container = me.container,\r | |
820 | listItems = me.listItems,\r | |
821 | infinite = me.getInfinite(),\r | |
822 | scrollElement = me.scrollElement,\r | |
823 | item, header, itemCls;\r | |
824 | \r | |
825 | config.$initParent = me;\r | |
826 | item = Ext.factory(config);\r | |
827 | delete config.$initParent;\r | |
828 | item.dataview = me;\r | |
829 | item.$height = config.minHeight;\r | |
830 | \r | |
831 | if (!infinite) {\r | |
832 | itemCls = me.getBaseCls() + '-item-relative';\r | |
833 | item.addCls(itemCls);\r | |
834 | }\r | |
835 | \r | |
836 | header = item.getHeader && item.getHeader();\r | |
837 | if (header) {\r | |
838 | if (!infinite) {\r | |
839 | header.addCls(itemCls);\r | |
840 | } else {\r | |
841 | header.setTranslatable({\r | |
842 | translationMethod: this.translationMethod\r | |
843 | });\r | |
844 | header.translate(0, -10000);\r | |
845 | \r | |
846 | scrollElement.insertFirst(header.renderElement);\r | |
847 | }\r | |
848 | }\r | |
849 | \r | |
850 | container.doAdd(item);\r | |
851 | listItems.push(item);\r | |
852 | \r | |
853 | return item;\r | |
854 | },\r | |
855 | \r | |
856 | setItemsCount: function(itemsCount, itemConfig) {\r | |
857 | var me = this,\r | |
858 | listItems = me.listItems,\r | |
859 | config = itemConfig || me.getListItemConfig(),\r | |
860 | difference = itemsCount - listItems.length,\r | |
861 | i;\r | |
862 | \r | |
863 | // This loop will create new items if the new itemsCount is higher than the amount of items we currently have\r | |
864 | for (i = 0; i < difference; i++) {\r | |
865 | me.createItem(config);\r | |
866 | }\r | |
867 | \r | |
868 | // This loop will destroy unneeded items if the new itemsCount is lower than the amount of items we currently have\r | |
869 | for (i = difference; i < 0; i++) {\r | |
870 | listItems.pop().destroy();\r | |
871 | }\r | |
872 | \r | |
873 | me.itemsCount = itemsCount;\r | |
874 | \r | |
875 | // Finally we update all the list items with the correct content\r | |
876 | me.updateAllListItems();\r | |
877 | \r | |
878 | //Android Stock bug where redraw is needed to show empty list\r | |
879 | if (Ext.browser.is.AndroidStock && me.container.element && itemsCount === 0 && difference !== 0) {\r | |
880 | me.container.element.redraw();\r | |
881 | }\r | |
882 | \r | |
883 | return me.listItems;\r | |
884 | },\r | |
885 | \r | |
886 | updateListItem: function(item, index, info) {\r | |
887 | var me = this,\r | |
888 | recordMap = me.recordMap,\r | |
889 | oldRecord = item.getRecord(),\r | |
890 | store = info.store,\r | |
891 | record = store.getAt(index),\r | |
892 | headerIndices = me.headerIndices,\r | |
893 | footerIndices = me.footerIndices,\r | |
894 | header = item.getHeader && item.getHeader(),\r | |
895 | scrollDockItems = me.scrollDockItems,\r | |
896 | updatedItems = me.updatedItems,\r | |
897 | infinite = me.getInfinite(),\r | |
898 | storeCount = store.getCount(),\r | |
899 | grouper = store.getGrouper(),\r | |
900 | itemCls = [],\r | |
901 | headerCls = [],\r | |
902 | itemRemoveCls = [info.headerCls, info.footerCls, info.firstCls, info.lastCls, info.selectedCls, info.stripeCls],\r | |
903 | headerRemoveCls = [info.headerCls, info.footerCls, info.firstCls, info.lastCls],\r | |
904 | ln, i, scrollDockItem, viewModel;\r | |
905 | \r | |
906 | // When we update a list item, the header and scrolldocks can make it have to be retransformed.\r | |
907 | // For that reason we want to always set the position to -10000 so that the next time we translate\r | |
908 | // all the pieces are transformed to the correct location\r | |
909 | if (infinite) {\r | |
910 | item.$position = -10000;\r | |
911 | }\r | |
912 | \r | |
913 | // We begin by hiding/showing the item and its header depending on a record existing at this index\r | |
914 | if (!record) {\r | |
915 | item.setRecord(null);\r | |
916 | if (oldRecord) {\r | |
917 | delete recordMap[oldRecord.internalId];\r | |
918 | }\r | |
919 | if (infinite) {\r | |
920 | item.translate(0, -10000);\r | |
921 | } else {\r | |
922 | item.hide();\r | |
923 | }\r | |
924 | \r | |
925 | if (header) {\r | |
926 | if (infinite) {\r | |
927 | header.translate(0, -10000);\r | |
928 | } else {\r | |
929 | header.hide();\r | |
930 | }\r | |
931 | }\r | |
932 | item.$hidden = true;\r | |
933 | return;\r | |
934 | } else if (item.$hidden) {\r | |
935 | if (!infinite) {\r | |
936 | item.show();\r | |
937 | }\r | |
938 | item.$hidden = false;\r | |
939 | }\r | |
940 | \r | |
941 | if (infinite) {\r | |
942 | updatedItems.push(item);\r | |
943 | }\r | |
944 | \r | |
945 | // If this item was previously used for the first record in the store, and now it will not be, then we hide\r | |
946 | // any scrollDockTop items and change the isFirst flag\r | |
947 | if (item.isFirst && index !== 0) {\r | |
948 | ln = scrollDockItems.top.length;\r | |
949 | for (i = 0; i < ln; i++) {\r | |
950 | scrollDockItem = scrollDockItems.top[i];\r | |
951 | if (infinite) {\r | |
952 | scrollDockItem.translate(0, -10000);\r | |
953 | }\r | |
954 | }\r | |
955 | item.isFirst = false;\r | |
956 | }\r | |
957 | \r | |
958 | // If this item was previously used for the last record in the store, and now it will not be, then we hide\r | |
959 | // any scrollDockBottom items and change the istLast flag\r | |
960 | if (item.isLast && index !== storeCount - 1) {\r | |
961 | ln = scrollDockItems.bottom.length;\r | |
962 | for (i = 0; i < ln; i++) {\r | |
963 | scrollDockItem = scrollDockItems.bottom[i];\r | |
964 | if (infinite) {\r | |
965 | scrollDockItem.translate(0, -10000);\r | |
966 | }\r | |
967 | }\r | |
968 | item.isLast = false;\r | |
969 | }\r | |
970 | \r | |
971 | // If the item is already bound to this record then we shouldn't have to do anything\r | |
972 | if (item.$dataIndex !== index) {\r | |
973 | item.$dataIndex = index;\r | |
974 | me.fireEvent('itemindexchange', me, record, index, item);\r | |
975 | }\r | |
976 | \r | |
977 | // This is where we actually update the item with the record\r | |
978 | if (oldRecord === record) {\r | |
979 | item.updateRecord(record);\r | |
980 | } else {\r | |
981 | if (oldRecord) {\r | |
982 | delete recordMap[oldRecord.internalId];\r | |
983 | }\r | |
984 | recordMap[record.internalId] = item;\r | |
985 | item.setRecord(record);\r | |
986 | \r | |
987 | viewModel = item.getViewModel();\r | |
988 | if (viewModel) {\r | |
989 | viewModel.set('record', record);\r | |
990 | }\r | |
991 | }\r | |
992 | \r | |
993 | if (me.isSelected(record)) {\r | |
994 | itemCls.push(info.selectedCls);\r | |
995 | }\r | |
996 | \r | |
997 | if (info.grouped) {\r | |
998 | if (headerIndices[index]) {\r | |
999 | itemCls.push(info.headerCls);\r | |
1000 | headerCls.push(info.headerCls);\r | |
1001 | header.setHtml(grouper.getGroupString(record));\r | |
1002 | \r | |
1003 | if (!infinite) {\r | |
1004 | header.renderElement.insertBefore(item.renderElement);\r | |
1005 | }\r | |
1006 | header.show();\r | |
1007 | } else {\r | |
1008 | if (infinite) {\r | |
1009 | header.translate(0, -10000);\r | |
1010 | } else {\r | |
1011 | header.hide();\r | |
1012 | }\r | |
1013 | }\r | |
1014 | if (footerIndices[index]) {\r | |
1015 | itemCls.push(info.footerCls);\r | |
1016 | headerCls.push(info.footerCls);\r | |
1017 | }\r | |
1018 | }\r | |
1019 | \r | |
1020 | if (header && !info.grouped) {\r | |
1021 | if (infinite) {\r | |
1022 | header.translate(0, -10000);\r | |
1023 | } else {\r | |
1024 | header.hide();\r | |
1025 | }\r | |
1026 | }\r | |
1027 | \r | |
1028 | if (index === 0) {\r | |
1029 | item.isFirst = true;\r | |
1030 | itemCls.push(info.firstCls);\r | |
1031 | headerCls.push(info.firstCls);\r | |
1032 | \r | |
1033 | if (!info.grouped) {\r | |
1034 | itemCls.push(info.headerCls);\r | |
1035 | headerCls.push(info.headerCls);\r | |
1036 | }\r | |
1037 | \r | |
1038 | if (!infinite) {\r | |
1039 | for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {\r | |
1040 | scrollDockItem = scrollDockItems.top[i];\r | |
1041 | if (info.grouped) {\r | |
1042 | scrollDockItem.renderElement.insertBefore(header.renderElement);\r | |
1043 | } else {\r | |
1044 | scrollDockItem.renderElement.insertBefore(item.renderElement);\r | |
1045 | }\r | |
1046 | }\r | |
1047 | }\r | |
1048 | }\r | |
1049 | \r | |
1050 | if (index === storeCount - 1) {\r | |
1051 | item.isLast = true;\r | |
1052 | itemCls.push(info.lastCls);\r | |
1053 | headerCls.push(info.lastCls);\r | |
1054 | \r | |
1055 | if (!info.grouped) {\r | |
1056 | itemCls.push(info.footerCls);\r | |
1057 | headerCls.push(info.footerCls);\r | |
1058 | }\r | |
1059 | \r | |
1060 | if (!infinite) {\r | |
1061 | for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {\r | |
1062 | scrollDockItem = scrollDockItems.bottom[i];\r | |
1063 | scrollDockItem.renderElement.insertAfter(item.renderElement);\r | |
1064 | }\r | |
1065 | }\r | |
1066 | }\r | |
1067 | \r | |
1068 | if (info.striped && index % 2 === 1) {\r | |
1069 | itemCls.push(info.stripeCls);\r | |
1070 | }\r | |
1071 | \r | |
1072 | item.renderElement.replaceCls(itemRemoveCls, itemCls);\r | |
1073 | if (header) {\r | |
1074 | header.renderElement.replaceCls(headerRemoveCls, headerCls);\r | |
1075 | }\r | |
1076 | },\r | |
1077 | \r | |
1078 | updateAllListItems: function() {\r | |
1079 | var me = this,\r | |
1080 | store, items, info, topRenderedIndex, i, ln;\r | |
1081 | \r | |
1082 | if (!me.initialized) {\r | |
1083 | return;\r | |
1084 | }\r | |
1085 | \r | |
1086 | store = me.getStore();\r | |
1087 | items = me.listItems;\r | |
1088 | info = me.getListItemInfo();\r | |
1089 | topRenderedIndex = me.topRenderedIndex;\r | |
1090 | \r | |
1091 | if (store) {\r | |
1092 | for (i = 0, ln = items.length; i < ln; i++) {\r | |
1093 | me.updateListItem(items[i], topRenderedIndex + i, info);\r | |
1094 | }\r | |
1095 | }\r | |
1096 | \r | |
1097 | if (me.isPainted()) {\r | |
1098 | if (me.getInfinite() && store && store.getCount()) {\r | |
1099 | me.handleItemHeights();\r | |
1100 | }\r | |
1101 | me.refreshScroller();\r | |
1102 | }\r | |
1103 | },\r | |
1104 | \r | |
1105 | doRefresh: function() {\r | |
1106 | var me = this,\r | |
1107 | infinite = me.getInfinite(),\r | |
1108 | scroller = me.container.getScrollable(),\r | |
1109 | storeCount = me.getStore().getCount();\r | |
1110 | \r | |
1111 | if (infinite) {\r | |
1112 | me.getItemMap().populate(storeCount, this.topRenderedIndex);\r | |
1113 | }\r | |
1114 | \r | |
1115 | if (me.getGrouped()) {\r | |
1116 | me.refreshHeaderIndices();\r | |
1117 | }\r | |
1118 | \r | |
1119 | // This will refresh the items on the screen with the new data\r | |
1120 | if (storeCount) {\r | |
1121 | me.hideScrollDockItems();\r | |
1122 | me.hideEmptyText();\r | |
1123 | if (!infinite) {\r | |
1124 | me.setItemsCount(storeCount);\r | |
1125 | if (me.getScrollToTopOnRefresh()) {\r | |
1126 | scroller.scrollTo(0, 0);\r | |
1127 | }\r | |
1128 | } else {\r | |
1129 | if (me.getScrollToTopOnRefresh()) {\r | |
1130 | me.topRenderedIndex = 0;\r | |
1131 | me.topVisibleIndex = 0;\r | |
1132 | scroller.scrollTo(null, 0);\r | |
1133 | }\r | |
1134 | me.updateAllListItems();\r | |
1135 | }\r | |
1136 | } else {\r | |
1137 | me.onStoreClear();\r | |
1138 | }\r | |
1139 | },\r | |
1140 | \r | |
1141 | updateStore: function(store, oldStore) {\r | |
1142 | var me = this,\r | |
1143 | container = me.container;\r | |
1144 | \r | |
1145 | me.callParent([store, oldStore]);\r | |
1146 | \r | |
1147 | if (me._fireResizeOnNextLoad && me.hasLoadedStore) {\r | |
1148 | me._fireResizeOnNextLoad = false;\r | |
1149 | me.onContainerResize(container, { height: container.element.getHeight() });\r | |
1150 | }\r | |
1151 | },\r | |
1152 | \r | |
1153 | onLoad: function(store) {\r | |
1154 | var me = this,\r | |
1155 | container = me.container;\r | |
1156 | \r | |
1157 | me.callParent([store]);\r | |
1158 | \r | |
1159 | if (me._fireResizeOnNextLoad) {\r | |
1160 | me._fireResizeOnNextLoad = false;\r | |
1161 | me.onContainerResize(container, { height: container.element.getHeight() });\r | |
1162 | }\r | |
1163 | },\r | |
1164 | \r | |
1165 | onContainerResize: function(container, size) {\r | |
1166 | var me = this,\r | |
1167 | store = me.getStore(),\r | |
1168 | currentVisibleCount, newVisibleCount, minHeight, listItems, itemMap, itemConfig;\r | |
1169 | \r | |
1170 | if (!me.headerHeight) {\r | |
1171 | me.headerHeight = parseInt(me.pinnedHeader.renderElement.getHeight(), 10);\r | |
1172 | }\r | |
1173 | \r | |
1174 | if (me.getInfinite()) {\r | |
1175 | itemMap = me.getItemMap();\r | |
1176 | minHeight = itemMap.getMinimumHeight();\r | |
1177 | \r | |
1178 | if (!store || (!store.getCount() && !store.isLoaded())) {\r | |
1179 | // If the store is not yet loaded we can't measure the height of the first item\r | |
1180 | // to determine minHeight\r | |
1181 | // TODO: refactor\r | |
1182 | me._fireResizeOnNextLoad = true;\r | |
1183 | return;\r | |
1184 | }\r | |
1185 | \r | |
1186 | if (!minHeight) {\r | |
1187 | listItems = me.listItems;\r | |
1188 | \r | |
1189 | // If there was no itemHeight/minHeight specified, we measure and cache\r | |
1190 | // the height of the first item for purposes of buffered rendering\r | |
1191 | // caluclations.\r | |
1192 | // TODO: this won't handle variable row heights\r | |
1193 | \r | |
1194 | if (!listItems.length) {\r | |
1195 | // make sure the list contains at least one item so that we can measure\r | |
1196 | // its height from the dom. Don't worry about ending up with the wrong\r | |
1197 | // number of items - it will be corrected when we invoke setItemsCount\r | |
1198 | // shortly\r | |
1199 | itemConfig = me.getListItemConfig();\r | |
1200 | me.createItem(itemConfig);\r | |
1201 | me.updateListItem(listItems[0], 0, me.getListItemInfo());\r | |
1202 | me.visibleCount++;\r | |
1203 | \r | |
1204 | }\r | |
1205 | \r | |
1206 | minHeight = listItems[0].element.getHeight();\r | |
1207 | // cache the minimum height on the itemMap for next time\r | |
1208 | itemMap.setMinimumHeight(minHeight);\r | |
1209 | me.getItemMap().populate(me.getStore().getCount(), me.topRenderedIndex);\r | |
1210 | }\r | |
1211 | \r | |
1212 | currentVisibleCount = me.visibleCount;\r | |
1213 | newVisibleCount = Math.ceil(size.height / minHeight);\r | |
1214 | \r | |
1215 | if (newVisibleCount != currentVisibleCount) {\r | |
1216 | me.visibleCount = newVisibleCount;\r | |
1217 | me.setItemsCount(newVisibleCount + me.getBufferSize(), itemConfig);\r | |
1218 | // This is a private event used by some plugins\r | |
1219 | me.fireEvent('updatevisiblecount', this, newVisibleCount, currentVisibleCount);\r | |
1220 | }\r | |
1221 | } else if (me.listItems.length && me.getGrouped() && me.getPinHeaders()) {\r | |
1222 | // Whenever the container resizes, headers might be in different locations. For this reason\r | |
1223 | // we refresh the header position map\r | |
1224 | me.updateHeaderMap();\r | |
1225 | }\r | |
1226 | },\r | |
1227 | \r | |
1228 | refreshScroller: function(skipOnRefresh) {\r | |
1229 | var me = this,\r | |
1230 | scroller = me.container.getScrollable(),\r | |
1231 | infinite = me.getInfinite(),\r | |
1232 | height, scrollSize;\r | |
1233 | \r | |
1234 | if (infinite) {\r | |
1235 | height = me.getItemMap().getTotalHeight();\r | |
1236 | scrollSize = scroller.getSize();\r | |
1237 | \r | |
1238 | if (height != scrollSize.y) {\r | |
1239 | scroller.setSize({\r | |
1240 | // When using a TouchScroller we can pass the x scrollSize to prevent\r | |
1241 | // the scroller from reading the DOM to determine the x size.\r | |
1242 | // When using a DomScroller we pass null so that only the y size gets set\r | |
1243 | // (DomScroller does not need to read the dom to determine missing dimensions)\r | |
1244 | x: scroller.isTouchScroller ? scrollSize.x : null,\r | |
1245 | y: height\r | |
1246 | });\r | |
1247 | }\r | |
1248 | \r | |
1249 | if (!skipOnRefresh) {\r | |
1250 | me.onScrollerRefresh(scroller);\r | |
1251 | }\r | |
1252 | } else {\r | |
1253 | if (me.getGrouped() && me.getPinHeaders()) {\r | |
1254 | me.updateHeaderMap();\r | |
1255 | }\r | |
1256 | scroller.refresh();\r | |
1257 | }\r | |
1258 | },\r | |
1259 | \r | |
1260 | updateHeaderMap: function() {\r | |
1261 | var me = this,\r | |
1262 | headerMap = me.headerMap,\r | |
1263 | headerIndices = me.headerIndices,\r | |
1264 | header, i;\r | |
1265 | \r | |
1266 | headerMap.length = 0;\r | |
1267 | for (i in headerIndices) {\r | |
1268 | if (headerIndices.hasOwnProperty(i)) {\r | |
1269 | header = me.getItemAt(i).getHeader();\r | |
1270 | headerMap.push(header.renderElement.dom.offsetTop);\r | |
1271 | }\r | |
1272 | }\r | |
1273 | },\r | |
1274 | \r | |
1275 | applyVariableHeights: function(value) {\r | |
1276 | if (!this.getInfinite()) {\r | |
1277 | return true;\r | |
1278 | }\r | |
1279 | return value;\r | |
1280 | },\r | |
1281 | \r | |
1282 | applyDefaultType: function(defaultType) {\r | |
1283 | if (!defaultType) {\r | |
1284 | defaultType = this.getUseSimpleItems() ? 'simplelistitem' : 'listitem';\r | |
1285 | }\r | |
1286 | return defaultType;\r | |
1287 | },\r | |
1288 | \r | |
1289 | applyItemMap: function(itemMap) {\r | |
1290 | return Ext.factory(itemMap, Ext.util.PositionMap, this.getItemMap());\r | |
1291 | },\r | |
1292 | \r | |
1293 | updateItemHeight: function(itemHeight) {\r | |
1294 | this.getItemMap().setMinimumHeight(itemHeight);\r | |
1295 | },\r | |
1296 | \r | |
1297 | applyIndexBar: function(indexBar) {\r | |
1298 | return Ext.factory(indexBar, Ext.dataview.IndexBar, this.getIndexBar());\r | |
1299 | },\r | |
1300 | \r | |
1301 | updatePinHeaders: function(pinnedHeaders) {\r | |
1302 | if (this.isPainted()) {\r | |
1303 | this.pinnedHeader.translate(0, pinnedHeaders ? this.pinnedHeader.$position : -10000);\r | |
1304 | }\r | |
1305 | },\r | |
1306 | \r | |
1307 | updateItemTpl: function(newTpl) {\r | |
1308 | var me = this,\r | |
1309 | listItems = me.listItems,\r | |
1310 | ln = listItems.length || 0,\r | |
1311 | i, listItem;\r | |
1312 | \r | |
1313 | for (i = 0; i < ln; i++) {\r | |
1314 | listItem = listItems[i];\r | |
1315 | listItem.setTpl(newTpl);\r | |
1316 | }\r | |
1317 | \r | |
1318 | me.updateAllListItems();\r | |
1319 | },\r | |
1320 | \r | |
1321 | updateItemCls: function(newCls, oldCls) {\r | |
1322 | var items = this.listItems,\r | |
1323 | ln = items.length,\r | |
1324 | i, item;\r | |
1325 | \r | |
1326 | for (i = 0; i < ln; i++) {\r | |
1327 | item = items[i];\r | |
1328 | item.removeCls(oldCls);\r | |
1329 | item.addCls(newCls);\r | |
1330 | }\r | |
1331 | },\r | |
1332 | \r | |
1333 | updateIndexBar: function(indexBar, oldIndexBar) {\r | |
1334 | var me = this,\r | |
1335 | scrollViewElement = me.scrollViewElement;\r | |
1336 | \r | |
1337 | if (oldIndexBar) {\r | |
1338 | oldIndexBar.un({\r | |
1339 | index: 'onIndex',\r | |
1340 | scope: me\r | |
1341 | });\r | |
1342 | \r | |
1343 | if (!indexBar) {\r | |
1344 | me.element.removeCls(me.getBaseCls() + '-indexed');\r | |
1345 | }\r | |
1346 | \r | |
1347 | if (scrollViewElement) {\r | |
1348 | scrollViewElement.removeChild(oldIndexBar.renderElement);\r | |
1349 | }\r | |
1350 | }\r | |
1351 | \r | |
1352 | if (indexBar) {\r | |
1353 | indexBar.on({\r | |
1354 | index: 'onIndex',\r | |
1355 | scope: me\r | |
1356 | });\r | |
1357 | \r | |
1358 | if (!oldIndexBar) {\r | |
1359 | me.element.addCls(me.getBaseCls() + '-indexed');\r | |
1360 | }\r | |
1361 | \r | |
1362 | if (scrollViewElement) {\r | |
1363 | scrollViewElement.appendChild(indexBar.renderElement);\r | |
1364 | }\r | |
1365 | }\r | |
1366 | },\r | |
1367 | \r | |
1368 | updateGrouped: function(grouped) {\r | |
1369 | if (this.initialized) {\r | |
1370 | this.handleGroupChange();\r | |
1371 | }\r | |
1372 | },\r | |
1373 | \r | |
1374 | onStoreGroupChange: function() {\r | |
1375 | if (this.initialized) {\r | |
1376 | this.handleGroupChange();\r | |
1377 | }\r | |
1378 | },\r | |
1379 | \r | |
1380 | onStoreAdd: function() {\r | |
1381 | this.doRefresh();\r | |
1382 | },\r | |
1383 | \r | |
1384 | onStoreRemove: function() {\r | |
1385 | this.doRefresh();\r | |
1386 | },\r | |
1387 | \r | |
1388 | onStoreUpdate: function(store, record, type, modifiedFieldNames, info) {\r | |
1389 | var me = this,\r | |
1390 | index, item;\r | |
1391 | \r | |
1392 | if (me.getInfinite() || info.indexChanged) {\r | |
1393 | me.doRefresh();\r | |
1394 | } else {\r | |
1395 | index = store.indexOf(record);\r | |
1396 | item = me.listItems[index];\r | |
1397 | if (item) {\r | |
1398 | me.updateListItem(item, index, me.getListItemInfo());\r | |
1399 | }\r | |
1400 | }\r | |
1401 | },\r | |
1402 | \r | |
1403 | onStoreClear: function() {\r | |
1404 | var me = this,\r | |
1405 | scroller = me.container.getScrollable(),\r | |
1406 | infinite = me.getInfinite();\r | |
1407 | \r | |
1408 | if (me.pinnedHeader) {\r | |
1409 | me.pinnedHeader.translate(0, -10000);\r | |
1410 | }\r | |
1411 | \r | |
1412 | me.getItemMap().populate(0, 0);\r | |
1413 | \r | |
1414 | if (!infinite) {\r | |
1415 | me.setItemsCount(0);\r | |
1416 | } else {\r | |
1417 | me.topRenderedIndex = 0;\r | |
1418 | me.topVisibleIndex = 0;\r | |
1419 | me.updateAllListItems();\r | |
1420 | }\r | |
1421 | \r | |
1422 | scroller.scrollTo(null, 0);\r | |
1423 | me.refreshScroller();\r | |
1424 | },\r | |
1425 | \r | |
1426 | showEmptyScrollDock: function() {\r | |
1427 | var me = this,\r | |
1428 | infinite = me.getInfinite(),\r | |
1429 | scrollDockItems = me.scrollDockItems,\r | |
1430 | offset = 0,\r | |
1431 | i, ln, item;\r | |
1432 | \r | |
1433 | for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {\r | |
1434 | item = scrollDockItems.top[i];\r | |
1435 | if (infinite) {\r | |
1436 | item.translate(0, offset);\r | |
1437 | offset += item.$scrollDockHeight;\r | |
1438 | } else {\r | |
1439 | this.scrollElement.appendChild(item.renderElement);\r | |
1440 | }\r | |
1441 | }\r | |
1442 | \r | |
1443 | for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {\r | |
1444 | item = scrollDockItems.bottom[i];\r | |
1445 | if (infinite) {\r | |
1446 | item.translate(0, offset);\r | |
1447 | offset += item.$scrollDockHeight;\r | |
1448 | } else {\r | |
1449 | this.scrollElement.appendChild(item.renderElement);\r | |
1450 | }\r | |
1451 | }\r | |
1452 | },\r | |
1453 | \r | |
1454 | hideScrollDockItems: function() {\r | |
1455 | var me = this,\r | |
1456 | infinite = me.getInfinite(),\r | |
1457 | scrollDockItems = me.scrollDockItems,\r | |
1458 | i, ln, item;\r | |
1459 | \r | |
1460 | if (!infinite) {\r | |
1461 | return;\r | |
1462 | }\r | |
1463 | \r | |
1464 | for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {\r | |
1465 | item = scrollDockItems.top[i];\r | |
1466 | item.translate(0, -10000);\r | |
1467 | }\r | |
1468 | \r | |
1469 | for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {\r | |
1470 | item = scrollDockItems.bottom[i];\r | |
1471 | item.translate(0, -10000);\r | |
1472 | }\r | |
1473 | },\r | |
1474 | \r | |
1475 | /**\r | |
1476 | * Gets a list item by record.\r | |
1477 | * @param {Ext.data.Model} The record\r | |
1478 | * @return {Ext.dataview.component.(Simple)ListItem} The list item, if found.\r | |
1479 | * `null` if no matching item exists.\r | |
1480 | */\r | |
1481 | getItem: function(record) {\r | |
1482 | var item;\r | |
1483 | if (record) {\r | |
1484 | item = this.recordMap[record.internalId];\r | |
1485 | }\r | |
1486 | return item || null;\r | |
1487 | },\r | |
1488 | \r | |
1489 | /**\r | |
1490 | * Returns an item at the specified index.\r | |
1491 | * @param {Number} index Index of the item.\r | |
1492 | * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.\r | |
1493 | */\r | |
1494 | getItemAt: function(index) {\r | |
1495 | var listItems = this.listItems,\r | |
1496 | ln = listItems.length,\r | |
1497 | i, listItem;\r | |
1498 | \r | |
1499 | for (i = 0; i < ln; i++) {\r | |
1500 | listItem = listItems[i];\r | |
1501 | if (listItem.$dataIndex == index) {\r | |
1502 | return listItem;\r | |
1503 | }\r | |
1504 | }\r | |
1505 | },\r | |
1506 | \r | |
1507 | /**\r | |
1508 | * Returns an index for the specified item.\r | |
1509 | * @param {Number} item The item to locate.\r | |
1510 | * @return {Number} Index for the specified item.\r | |
1511 | */\r | |
1512 | getItemIndex: function(item) {\r | |
1513 | return item.$dataIndex;\r | |
1514 | },\r | |
1515 | \r | |
1516 | /**\r | |
1517 | * Returns an array of the current items in the DataView.\r | |
1518 | * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.\r | |
1519 | */\r | |
1520 | getViewItems: function() {\r | |
1521 | return this.listItems;\r | |
1522 | },\r | |
1523 | \r | |
1524 | getListItemInfo: function() {\r | |
1525 | var me = this,\r | |
1526 | baseCls = me.getBaseCls();\r | |
1527 | \r | |
1528 | return {\r | |
1529 | store: me.getStore(),\r | |
1530 | grouped: me.isGrouping(),\r | |
1531 | baseCls: baseCls,\r | |
1532 | selectedCls: me.getSelectedCls(),\r | |
1533 | headerCls: baseCls + '-header-wrap',\r | |
1534 | footerCls: baseCls + '-footer-wrap',\r | |
1535 | firstCls: baseCls + '-item-first',\r | |
1536 | lastCls: baseCls + '-item-last',\r | |
1537 | stripeCls: baseCls + '-item-odd',\r | |
1538 | striped: me.getStriped(),\r | |
1539 | itemMap: me.getItemMap(),\r | |
1540 | defaultItemHeight: me.getItemHeight()\r | |
1541 | };\r | |
1542 | },\r | |
1543 | \r | |
1544 | getListItemConfig: function() {\r | |
1545 | var me = this,\r | |
1546 | minimumHeight = me.getItemMap().getMinimumHeight(),\r | |
1547 | config = {\r | |
1548 | xtype: me.getDefaultType(),\r | |
1549 | tpl: me.getItemTpl(),\r | |
1550 | minHeight: minimumHeight,\r | |
1551 | cls: me.getItemCls()\r | |
1552 | };\r | |
1553 | \r | |
1554 | if (me.getInfinite()) {\r | |
1555 | config.translatable = {\r | |
1556 | translationMethod: this.translationMethod\r | |
1557 | };\r | |
1558 | }\r | |
1559 | \r | |
1560 | if (!me.getVariableHeights()) {\r | |
1561 | config.height = minimumHeight;\r | |
1562 | }\r | |
1563 | \r | |
1564 | return Ext.merge(config, me.getItemConfig());\r | |
1565 | },\r | |
1566 | \r | |
1567 | refreshHeaderIndices: function() {\r | |
1568 | var me = this,\r | |
1569 | store = me.getStore(),\r | |
1570 | storeLn = store && store.getCount(),\r | |
1571 | groups = store.getGrouper() ? store.getGroups() : null,\r | |
1572 | grouped = me.getGrouped(),\r | |
1573 | headerIndices = me.headerIndices = {},\r | |
1574 | footerIndices = me.footerIndices = {},\r | |
1575 | i, previousIndex, firstGroupedRecord, storeIndex, groupLn;\r | |
1576 | \r | |
1577 | if (!grouped || !groups) {\r | |
1578 | return footerIndices;\r | |
1579 | }\r | |
1580 | groupLn = groups.length;\r | |
1581 | me.groups = groups;\r | |
1582 | \r | |
1583 | for (i = 0; i < groupLn; i++) {\r | |
1584 | firstGroupedRecord = groups.getAt(i).getAt(0);\r | |
1585 | storeIndex = store.indexOf(firstGroupedRecord);\r | |
1586 | headerIndices[storeIndex] = true;\r | |
1587 | \r | |
1588 | previousIndex = storeIndex - 1;\r | |
1589 | if (previousIndex >= 0) {\r | |
1590 | footerIndices[previousIndex] = true;\r | |
1591 | }\r | |
1592 | }\r | |
1593 | \r | |
1594 | footerIndices[storeLn - 1] = true;\r | |
1595 | \r | |
1596 | return headerIndices;\r | |
1597 | },\r | |
1598 | \r | |
1599 | onIndex: function(indexBar, index) {\r | |
1600 | var me = this,\r | |
1601 | key = index.toLowerCase(),\r | |
1602 | store = me.getStore(),\r | |
1603 | groups = store.getGroups(),\r | |
1604 | ln = groups.length,\r | |
1605 | group, groupKey, i, closest;\r | |
1606 | \r | |
1607 | for (i = 0; i < ln; i++) {\r | |
1608 | group = groups.getAt(i);\r | |
1609 | groupKey = group.getGroupKey().toLowerCase();\r | |
1610 | if (groupKey >= key) {\r | |
1611 | closest = group;\r | |
1612 | break;\r | |
1613 | }\r | |
1614 | else {\r | |
1615 | closest = group;\r | |
1616 | }\r | |
1617 | }\r | |
1618 | \r | |
1619 | if (closest) {\r | |
1620 | this.scrollToRecord(closest.getAt(0));\r | |
1621 | }\r | |
1622 | },\r | |
1623 | \r | |
1624 | /**\r | |
1625 | *\r | |
1626 | * Scrolls the list so that the specified record is at the top.\r | |
1627 | *\r | |
1628 | * @param record {Ext.data.Model} Record in the lists store to scroll to\r | |
1629 | * @param animate {Boolean} Determines if scrolling is animated to a cut\r | |
1630 | * @param overscroll {Boolean} Determines if list can be overscrolled\r | |
1631 | */\r | |
1632 | scrollToRecord: function(record, animate, overscroll) {\r | |
1633 | var me = this,\r | |
1634 | scroller = me.container.getScrollable(),\r | |
1635 | store = me.getStore(),\r | |
1636 | index = store.indexOf(record),\r | |
1637 | header;\r | |
1638 | \r | |
1639 | //stop the scroller from scrolling\r | |
1640 | scroller.stopAnimation();\r | |
1641 | \r | |
1642 | //make sure the new offsetTop is not out of bounds for the scroller\r | |
1643 | var elementHeight = scroller.getElement().getHeight(),\r | |
1644 | scrollHeight = scroller.getSize().y,\r | |
1645 | maxOffset = scrollHeight - elementHeight,\r | |
1646 | offset, item;\r | |
1647 | \r | |
1648 | if (me.getInfinite()) {\r | |
1649 | offset = me.getItemMap().map[index];\r | |
1650 | }\r | |
1651 | else {\r | |
1652 | item = me.listItems[index];\r | |
1653 | header = item.getHeader && item.getHeader();\r | |
1654 | \r | |
1655 | if (header && header.isPainted()) {\r | |
1656 | offset = header.renderElement.dom.offsetTop;\r | |
1657 | }\r | |
1658 | else {\r | |
1659 | offset = item.renderElement.dom.offsetTop;\r | |
1660 | }\r | |
1661 | }\r | |
1662 | \r | |
1663 | if (!overscroll) {\r | |
1664 | offset = Math.min(offset, maxOffset);\r | |
1665 | }\r | |
1666 | \r | |
1667 | scroller.scrollTo(0, offset, !!animate);\r | |
1668 | },\r | |
1669 | \r | |
1670 | onItemAdd: function(item) {\r | |
1671 | var me = this,\r | |
1672 | config = item.config;\r | |
1673 | \r | |
1674 | if (config.scrollDock) {\r | |
1675 | if (config.scrollDock == 'bottom') {\r | |
1676 | me.scrollDockItems.bottom.push(item);\r | |
1677 | } else {\r | |
1678 | me.scrollDockItems.top.push(item);\r | |
1679 | }\r | |
1680 | \r | |
1681 | if (me.getInfinite()) {\r | |
1682 | item.on({\r | |
1683 | resize: 'onScrollDockItemResize',\r | |
1684 | scope: this\r | |
1685 | });\r | |
1686 | \r | |
1687 | item.addCls(me.getBaseCls() + '-scrolldockitem');\r | |
1688 | item.setTranslatable({\r | |
1689 | translationMethod: this.translationMethod\r | |
1690 | });\r | |
1691 | item.translate(0, -10000);\r | |
1692 | item.$scrollDockHeight = 0;\r | |
1693 | }\r | |
1694 | \r | |
1695 | me.container.doAdd(item);\r | |
1696 | } else {\r | |
1697 | me.callParent(arguments);\r | |
1698 | }\r | |
1699 | },\r | |
1700 | \r | |
1701 | /**\r | |
1702 | * Returns all the items that are docked in the scroller in this list.\r | |
1703 | * @return {Array} An array of the scrollDock items\r | |
1704 | */\r | |
1705 | getScrollDockedItems: function() {\r | |
1706 | return this.scrollDockItems.bottom.slice().concat(this.scrollDockItems.top.slice());\r | |
1707 | },\r | |
1708 | \r | |
1709 | onScrollDockItemResize: function(dockItem, size) {\r | |
1710 | var me = this,\r | |
1711 | items = me.listItems,\r | |
1712 | ln = items.length,\r | |
1713 | i, item;\r | |
1714 | \r | |
1715 | Ext.getCmp(dockItem.id).$scrollDockHeight = size.height;\r | |
1716 | \r | |
1717 | for (i = 0; i < ln; i++) {\r | |
1718 | item = items[i];\r | |
1719 | if (item.isLast) {\r | |
1720 | me.updatedItems.push(item);\r | |
1721 | if (me.isPainted()) {\r | |
1722 | me.refreshScroller();\r | |
1723 | }\r | |
1724 | break;\r | |
1725 | }\r | |
1726 | }\r | |
1727 | },\r | |
1728 | \r | |
1729 | onItemTouchStart: function(e) {\r | |
1730 | this.container.innerElement.on({\r | |
1731 | touchmove: 'onItemTouchMove',\r | |
1732 | delegate: '.' + Ext.baseCSSPrefix + 'list-item',\r | |
1733 | single: true,\r | |
1734 | scope: this\r | |
1735 | });\r | |
1736 | this.callParent(this.parseEvent(e));\r | |
1737 | },\r | |
1738 | \r | |
1739 | onItemTouchMove: function(e) {\r | |
1740 | this.callParent(this.parseEvent(e));\r | |
1741 | },\r | |
1742 | \r | |
1743 | onItemTouchEnd: function(e) {\r | |
1744 | this.container.innerElement.un({\r | |
1745 | touchmove: 'onItemTouchMove',\r | |
1746 | delegate: '.' + Ext.baseCSSPrefix + 'list-item',\r | |
1747 | scope: this\r | |
1748 | });\r | |
1749 | this.callParent(this.parseEvent(e));\r | |
1750 | },\r | |
1751 | \r | |
1752 | onItemTap: function(e) {\r | |
1753 | this.callParent(this.parseEvent(e));\r | |
1754 | },\r | |
1755 | \r | |
1756 | onItemTapHold: function(e) {\r | |
1757 | this.callParent(this.parseEvent(e));\r | |
1758 | },\r | |
1759 | \r | |
1760 | onItemSingleTap: function(e) {\r | |
1761 | this.callParent(this.parseEvent(e));\r | |
1762 | },\r | |
1763 | \r | |
1764 | onItemDoubleTap: function(e) {\r | |
1765 | this.callParent(this.parseEvent(e));\r | |
1766 | },\r | |
1767 | \r | |
1768 | onItemSwipe: function(e) {\r | |
1769 | this.callParent(this.parseEvent(e));\r | |
1770 | },\r | |
1771 | \r | |
1772 | parseEvent: function(e) {\r | |
1773 | var me = this,\r | |
1774 | target = Ext.fly(e.currentTarget).findParent('.' + Ext.baseCSSPrefix + 'list-item', 8),\r | |
1775 | item = Ext.getCmp(target.id);\r | |
1776 | \r | |
1777 | return [me, item, item.$dataIndex, e];\r | |
1778 | },\r | |
1779 | \r | |
1780 | applyOnItemDisclosure: function(config) {\r | |
1781 | if (Ext.isFunction(config)) {\r | |
1782 | return {\r | |
1783 | scope: this,\r | |
1784 | handler: config\r | |
1785 | };\r | |
1786 | }\r | |
1787 | return config;\r | |
1788 | },\r | |
1789 | \r | |
1790 | handleItemDisclosure: function(e) {\r | |
1791 | var me = this,\r | |
1792 | item = Ext.getCmp(Ext.get(e.currentTarget).up('.x-list-item').id),\r | |
1793 | index = item.$dataIndex,\r | |
1794 | record = me.getStore().getAt(index);\r | |
1795 | \r | |
1796 | me.fireAction('disclose', [me, record, item, index, e], 'doDisclose');\r | |
1797 | },\r | |
1798 | \r | |
1799 | doDisclose: function(me, record, item, index, e) {\r | |
1800 | var onItemDisclosure = me.getOnItemDisclosure();\r | |
1801 | \r | |
1802 | if (onItemDisclosure && onItemDisclosure.handler) {\r | |
1803 | onItemDisclosure.handler.call(onItemDisclosure.scope || me, record, item, index, e);\r | |
1804 | }\r | |
1805 | },\r | |
1806 | \r | |
1807 | // apply to the selection model to maintain visual UI cues\r | |
1808 | onItemTrigger: function(me, index, target, record, e) {\r | |
1809 | if (!(this.getPreventSelectionOnDisclose() && Ext.fly(e.target).hasCls(this.getBaseCls() + '-disclosure'))) {\r | |
1810 | this.callParent(arguments);\r | |
1811 | }\r | |
1812 | },\r | |
1813 | \r | |
1814 | destroy: function() {\r | |
1815 | var me = this,\r | |
1816 | items = me.listItems,\r | |
1817 | ln = items.length,\r | |
1818 | i;\r | |
1819 | \r | |
1820 | if (me.pinnedHeader) {\r | |
1821 | me.pinnedHeader.destroy();\r | |
1822 | me.pinnedHeader = null;\r | |
1823 | }\r | |
1824 | \r | |
1825 | me.callParent();\r | |
1826 | \r | |
1827 | if (me.onIdleBound) {\r | |
1828 | Ext.AnimationQueue.unIdle(me.onAnimationIdle, me);\r | |
1829 | }\r | |
1830 | \r | |
1831 | for (i = 0; i < ln; i++) {\r | |
1832 | items[i].destroy();\r | |
1833 | }\r | |
1834 | me.recordMap = me.listItems = null;\r | |
1835 | },\r | |
1836 | \r | |
1837 | privates: {\r | |
1838 | handleGroupChange: function() {\r | |
1839 | var me = this,\r | |
1840 | grouped = me.isGrouping(),\r | |
1841 | baseCls = this.getBaseCls(),\r | |
1842 | infinite = me.getInfinite(),\r | |
1843 | pinnedHeader = me.pinnedHeader,\r | |
1844 | cls = baseCls + '-grouped',\r | |
1845 | unCls = baseCls + '-ungrouped';\r | |
1846 | \r | |
1847 | if (pinnedHeader) {\r | |
1848 | pinnedHeader.translate(0, -10000);\r | |
1849 | }\r | |
1850 | \r | |
1851 | if (grouped) {\r | |
1852 | me.addCls(cls);\r | |
1853 | me.removeCls(unCls);\r | |
1854 | } else {\r | |
1855 | me.addCls(unCls);\r | |
1856 | me.removeCls(cls);\r | |
1857 | }\r | |
1858 | \r | |
1859 | if (infinite) {\r | |
1860 | me.refreshHeaderIndices();\r | |
1861 | me.handleItemHeights();\r | |
1862 | }\r | |
1863 | me.updateAllListItems();\r | |
1864 | if (infinite) {\r | |
1865 | me.handleItemTransforms();\r | |
1866 | }\r | |
1867 | },\r | |
1868 | \r | |
1869 | isGrouping: function() {\r | |
1870 | return Boolean(this.getGrouped() && this.getStore().getGrouper());\r | |
1871 | }\r | |
1872 | }\r | |
1873 | });\r |