]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/view/AbstractView.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / view / AbstractView.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.view.AbstractView\r
3 * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.\r
4 * @private\r
5 */\r
6Ext.define('Ext.view.AbstractView', {\r
7 extend: 'Ext.Component',\r
8 requires: [\r
9 'Ext.LoadMask',\r
10 'Ext.CompositeElementLite',\r
11 'Ext.selection.DataViewModel',\r
12 'Ext.view.NavigationModel'\r
13 ],\r
14 mixins: [\r
15 'Ext.util.StoreHolder'\r
16 ],\r
17\r
18 inheritableStatics: {\r
19 /**\r
20 * @private\r
21 * @static\r
22 * @inheritable\r
23 */\r
24 getRecord: function(node) {\r
25 return this.getBoundView(node).getRecord(node);\r
26 },\r
27\r
28 /**\r
29 * @private\r
30 * @static\r
31 * @inheritable\r
32 */\r
33 getBoundView: function(node) {\r
34 return Ext.getCmp(node.getAttribute('data-boundView'));\r
35 }\r
36 },\r
37 \r
38 defaultBindProperty: 'store',\r
39\r
40 /**\r
41 * @private\r
42 * Used for buffered rendering.\r
43 */\r
44 renderBuffer: document.createElement('div'),\r
45\r
46 statics: {\r
47\r
48 /**\r
49 * @cfg {Number} [updateDelay=200] Global config for use when using {@link #throttledUpdate throttled view updating} if the data in the backing {@link Ext.data.Store store}\r
50 * is being changed rapidly, for example receiving changes from the server through a WebSocket connection.\r
51 *\r
52 * To avoid too-frequent view updates overloading the browser with style recalculation, layout and paint requests, updates can be {@link #throttledUpdate throttled} to \r
53 * coalesced, and applied at the interval specified in milliseconds.\r
54 */\r
55 updateDelay: 200,\r
56\r
57 queueRecordChange: function(view, store, record, operation, modifiedFieldNames) {\r
58 var me = this,\r
59 changeQueue = me.changeQueue || (me.changeQueue = {}),\r
60 recId = record.internalId,\r
61 recChange,\r
62 updated,\r
63 len, i, fieldName, value,\r
64 checkForReversion;\r
65\r
66 recChange = changeQueue[recId] || (changeQueue[recId] = {\r
67 operation: operation,\r
68 record: record,\r
69 data: {},\r
70 views: []\r
71 });\r
72\r
73 // Hash of original values\r
74 updated = recChange.data;\r
75\r
76 // Make sure this view is among those updated when record changes are flushed\r
77 Ext.Array.include(recChange.views, view);\r
78\r
79 // Note the following condition tests the result of an assignment statement.\r
80 // If we have been informed that specific fields have changed.\r
81 if (modifiedFieldNames && (len = modifiedFieldNames.length)) {\r
82 for (i = 0; i < len; i++) {\r
83 fieldName = modifiedFieldNames[i];\r
84 value = record.data[fieldName];\r
85\r
86 // More than one update is being performed...\r
87 if (updated.hasOwnProperty(fieldName)) {\r
88\r
89 // If the update is back to the original value, this may have reverted the record to original state\r
90 if (record.isEqual(updated[fieldName], value)) {\r
91 delete updated[fieldName];\r
92 checkForReversion = true;\r
93 }\r
94 }\r
95\r
96 // On first update, cache the original value\r
97 else {\r
98 updated[fieldName] = value;\r
99 }\r
100 }\r
101\r
102 // If the record has been returned to its original state, delete the queue entry.\r
103 // checkForReversion flag saves the expensive (on legacy browsers) call to Ext.Object.getKeys\r
104 if (checkForReversion && !Ext.Object.getKeys(updated).length) {\r
105 delete changeQueue[recId];\r
106 }\r
107 }\r
108\r
109 // Unpsecified fields have changed. We have to collect the whole data object.\r
110 else {\r
111 Ext.apply(updated, record.data);\r
112 }\r
113\r
114 // Create a task which will call on to the onFlushTick every updateDelay milliseconds.\r
115 if (!me.flushQueueTask) {\r
116 me.flushQueueTask = Ext.util.TaskManager.newTask({\r
117 // Queue the actual render flush on the next animation frame if available.\r
118 run: Ext.global.requestAnimationFrame ? Ext.Function.createAnimationFrame(me.onFlushTick, me) : Ext.Function.bind(me.onFlushTick, me),\r
119 interval: Ext.view.AbstractView.updateDelay,\r
120 repeat: 1\r
121 });\r
122 }\r
123 me.flushQueueTask.start();\r
124 },\r
125\r
126 /**\r
127 * @private\r
128 * On every flush (determined by updateDelay setting), ask the animation system to schedule a call to\r
129 * flushChangeQueue at the next animation frame.\r
130 */\r
131 onFlushTick: function() {\r
132 Ext.AnimationQueue.start(this.flushChangeQueue, this);\r
133 },\r
134\r
135 /**\r
136 * @private\r
137 * Flushes all queued field updates to the UI.\r
138 *\r
139 * Called in the context of the AbstractView class.\r
140 *\r
141 * The queue is shared across all Views so that there is only one global flush operation.\r
142 */\r
143 flushChangeQueue: function() {\r
144 // Maintainer: Note that "me" references AbstractView class\r
145 var me = this,\r
146 dirtyViews,\r
147 len,\r
148 changeQueue,\r
149 recChange,\r
150 recId,\r
151 i, view;\r
152\r
153 // If there is scrolling going on anywhere, requeue the flush operation.\r
154 if (Ext.isScrolling) {\r
155 me.flushQueueTask.start();\r
156 return;\r
157 }\r
158\r
159 changeQueue = me.changeQueue;\r
160\r
161 // Empty the view's changeQueue\r
162 this.changeQueue = {};\r
163\r
164 for (recId in changeQueue) {\r
165 recChange = changeQueue[recId];\r
166 dirtyViews = recChange.views;\r
167 len = dirtyViews.length;\r
168\r
169 // Loop through all the views which have outstanding changes.\r
170 for (i = 0; i < len; i++) {\r
171 view = dirtyViews[i];\r
172\r
173 // View may have been destroyed during the buffered phase.\r
174 if (!view.destroyed) {\r
175 view.handleUpdate(view.dataSource, recChange.record, recChange.operation, Ext.Object.getKeys(recChange.data));\r
176 }\r
177 }\r
178 }\r
179 Ext.AnimationQueue.stop(me.flushChangeQueue, me);\r
180 }\r
181 },\r
182\r
183 config: {\r
184 /**\r
185 * @cfg {Ext.data.Model} selection\r
186 * The selected model. Typically used with {@link #bind binding}.\r
187 */\r
188 selection: null,\r
189\r
190 /**\r
191 * @cfg {Ext.data.Store} store\r
192 * The {@link Ext.data.Store} to bind this DataView to.\r
193 * @since 2.3.0\r
194 */\r
195 store: 'ext-empty-store',\r
196\r
197 // @cmd-auto-dependency { aliasPrefix: 'view.navigation.' }\r
198 /**\r
199 * @private\r
200 * The {@link Ext.view.NavigationModel} [default] alias to use.\r
201 * @since 5.0.1\r
202 */\r
203 navigationModel: {\r
204 type: 'default'\r
205 },\r
206\r
207 // @cmd-auto-dependency { aliasPrefix: 'selection.' }\r
208 /**\r
209 * @cfg {Object/Ext.selection.DataViewModel} selectionModel\r
210 * The {@link Ext.selection.Model selection model} [dataviewmodel] config or alias to use.\r
211 * @since 5.1.0\r
212 */\r
213 selectionModel: {\r
214 type: 'dataviewmodel'\r
215 }\r
216 },\r
217\r
218 publishes: ['selection'],\r
219 twoWayBindable: ['selection'],\r
220\r
221 /**\r
222 * @cfg {Boolean} [throttledUpdate=false]\r
223 * Configure as `true` to have this view participate in the global throttled update queue which flushes store changes to the UI at a maximum rate\r
224 * determined by the {@link #updateDelay} setting.\r
225 */\r
226 throttledUpdate: false,\r
227\r
228 /**\r
229 * @cfg {String/String[]/Ext.XTemplate} tpl (required)\r
230 * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should\r
231 * be specified in the same format expected by the constructor of {@link Ext.XTemplate}. When a `tpl` is specified,\r
232 * this class assumes that records are rendered in the order they appear in the `{@link #store}`. If a custom `tpl`\r
233 * does not conform to this assumption, index values will be incorrect which may cause the view to misbehave.\r
234 * @since 2.3.0\r
235 */\r
236\r
237 /**\r
238 * @cfg {Boolean} [deferInitialRefresh=false]\r
239 * Configure as 'true` to defer the initial refresh of the view.\r
240 *\r
241 * This allows the View to execute its render and initial layout more quickly because the process will not be encumbered\r
242 * by the update of the view structure.\r
243 */\r
244 deferInitialRefresh: false,\r
245\r
246 /**\r
247 * @cfg {String} itemSelector (required)\r
248 * <b>This is a required setting</b>. A simple CSS selector (e.g. `div.some-class` or\r
249 * `span:first-child`) that will be used to determine what nodes this DataView will be\r
250 * working with. The itemSelector is used to map DOM nodes to records. As such, there should\r
251 * only be one root level element that matches the selector for each record. The itemSelector\r
252 * will be automatically configured if the {@link #itemTpl} config is used.\r
253 * \r
254 * new Ext.view.View({\r
255 * renderTo: Ext.getBody(),\r
256 * store: {\r
257 * fields: ['name'],\r
258 * data: [\r
259 * {name: 'Item 1'},\r
260 * {name: 'Item 2'}\r
261 * ]\r
262 * },\r
263 * tpl: [\r
264 * '<ul>',\r
265 * '<tpl for=".">',\r
266 * '<li>{name}</li>',\r
267 * '</tpl>',\r
268 * '</ul>'\r
269 * ],\r
270 * // Match the li, since each one maps to a record\r
271 * itemSelector: 'li'\r
272 * });\r
273 * \r
274 * @since 2.3.0\r
275 */\r
276\r
277 /**\r
278 * @cfg {String} itemCls\r
279 * Specifies the class to be assigned to each element in the view when used in conjunction with the\r
280 * {@link #itemTpl} configuration.\r
281 * @since 2.3.0\r
282 */\r
283 itemCls: Ext.baseCSSPrefix + 'dataview-item',\r
284\r
285 /**\r
286 * @cfg {String/String[]/Ext.XTemplate} itemTpl\r
287 * The inner portion of the item template to be rendered. Follows an XTemplate\r
288 * structure and will be placed inside of a tpl.\r
289 */\r
290\r
291 /**\r
292 * @cfg {String} overItemCls\r
293 * A CSS class to apply to each item in the view on mouseover.\r
294 * Setting this will automatically set {@link #trackOver} to `true`.\r
295 */\r
296\r
297 //<locale>\r
298 /**\r
299 * @cfg {String} loadingText\r
300 * A string to display during data load operations. If specified, this text will be\r
301 * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's\r
302 * contents will continue to display normally until the new data is loaded and the contents are replaced.\r
303 * @since 2.3.0\r
304 */\r
305 loadingText: 'Loading...',\r
306 //</locale>\r
307\r
308 /**\r
309 * @cfg {Boolean/Object} loadMask\r
310 * False to disable a load mask from displaying while the view is loading. This can also be a\r
311 * {@link Ext.LoadMask} configuration object.\r
312 */\r
313 loadMask: true,\r
314\r
315 /**\r
316 * @cfg {String} loadingCls\r
317 * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".\r
318 */\r
319\r
320 /**\r
321 * @cfg {Boolean} loadingUseMsg\r
322 * Whether or not to use the loading message.\r
323 * @private\r
324 */\r
325 loadingUseMsg: true,\r
326\r
327\r
328 /**\r
329 * @cfg {Number} loadingHeight\r
330 * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},\r
331 * if that is specified. This is useful to prevent the view's height from collapsing to zero when the\r
332 * loading mask is applied and there are no other contents in the data view.\r
333 */\r
334\r
335 /**\r
336 * @cfg {String} selectedItemCls\r
337 * A CSS class to apply to each selected item in the view.\r
338 */\r
339 selectedItemCls: Ext.baseCSSPrefix + 'item-selected',\r
340\r
341 //<locale>\r
342 /**\r
343 * @cfg {String} emptyText\r
344 * The text to display in the view when there is no data to display.\r
345 * Note that when using local data the emptyText will not be displayed unless you set\r
346 * the {@link #deferEmptyText} option to false.\r
347 * @since 2.3.0\r
348 */\r
349 emptyText: "",\r
350 //</locale>\r
351\r
352 /**\r
353 * @cfg {Boolean} deferEmptyText\r
354 * True to defer emptyText being applied until the store's first load.\r
355 * @since 2.3.0\r
356 */\r
357 deferEmptyText: true,\r
358\r
359 /**\r
360 * @cfg {Boolean} trackOver\r
361 * When `true` the {@link #overItemCls} will be applied to items when hovered over.\r
362 * This in return will also cause {@link Ext.view.View#highlightitem highlightitem} and\r
363 * {@link Ext.view.View#unhighlightitem unhighlightitem} events to be fired.\r
364 *\r
365 * Enabled automatically when the {@link #overItemCls} config is set.\r
366 *\r
367 * @since 2.3.0\r
368 */\r
369 trackOver: false,\r
370\r
371 /**\r
372 * @cfg {Boolean} blockRefresh\r
373 * Set this to true to ignore refresh events on the bound store. This is useful if\r
374 * you wish to provide custom transition animations via a plugin\r
375 * @since 3.4.0\r
376 */\r
377 blockRefresh: false,\r
378\r
379 /**\r
380 * @cfg {Boolean} [disableSelection=false]\r
381 * True to disable selection within the DataView. This configuration will lock the selection model\r
382 * that the DataView uses.\r
383 */\r
384\r
385 /**\r
386 * @cfg {Boolean} preserveScrollOnRefresh\r
387 * True to preserve scroll position across refresh operations.\r
388 */\r
389 preserveScrollOnRefresh: false,\r
390\r
391 /**\r
392 * @cfg {Boolean} [preserveScrollOnReload=false]\r
393 * True to preserve scroll position when the store is reloaded.\r
394 *\r
395 * You may want to configure this as `true` if you are using a {@link Ext.data.BufferedStore buffered store}\r
396 * and you require refreshes of the client side data state not to disturb the state of the UI.\r
397 *\r
398 * @since 5.1.1\r
399 */\r
400 preserveScrollOnReload: false,\r
401\r
402 ariaRole: 'listbox',\r
403 itemAriaRole: 'option',\r
404\r
405 /**\r
406 * @private\r
407 */\r
408 last: false,\r
409 focusable: true,\r
410 tabIndex: 0,\r
411\r
412 triggerEvent: 'itemclick',\r
413 triggerCtEvent: 'containerclick',\r
414\r
415 // Starts as true by default so that pn the leading edge of the first layout a refresh will be triggered.\r
416 // A refresh opereration sets this flag to false.\r
417 // When a refresh is requested using refreshView, the request may be deferred because of hidden or collapsed state.\r
418 // This is done by setting the refreshNeeded flag to true, and the the next layout will trigger refresh.\r
419 refreshNeeded: true,\r
420\r
421 updateSuspendCounter: 0,\r
422\r
423 addCmpEvents: Ext.emptyFn,\r
424\r
425 /**\r
426 * @event beforerefresh\r
427 * Fires before the view is refreshed\r
428 * @param {Ext.view.View} this The DataView object\r
429 */\r
430\r
431 /**\r
432 * @event refresh\r
433 * Fires when the view is refreshed\r
434 * @param {Ext.view.View} this The DataView object\r
435 */\r
436\r
437 /**\r
438 * @event viewready\r
439 * Fires when the View's item elements representing Store items has been rendered. No items will be available\r
440 * for selection until this event fires.\r
441 * @param {Ext.view.View} this\r
442 */\r
443\r
444 /**\r
445 * @event itemupdate\r
446 * Fires when the node associated with an individual record is updated\r
447 * @param {Ext.data.Model} record The model instance\r
448 * @param {Number} index The index of the record\r
449 * @param {HTMLElement} node The node that has just been updated\r
450 */\r
451\r
452 /**\r
453 * @event itemadd\r
454 * Fires when the nodes associated with an recordset have been added to the underlying store\r
455 * @param {Ext.data.Model[]} records The model instance\r
456 * @param {Number} index The index at which the set of records was inserted\r
457 * @param {HTMLElement[]} node The node that has just been updated\r
458 */\r
459\r
460 /**\r
461 * @event itemremove\r
462 * Fires when the node associated with an individual record is removed\r
463 * @param {Ext.data.Model[]} records The model instances removed\r
464 * @param {Number} index The index from which the records wer removed\r
465 * @param {HTMLElement[]} item The view items removed\r
466 * @param {Ext.view.View} view The view removing the item\r
467 */\r
468\r
469 constructor: function(config) {\r
470 if (config && config.selModel) {\r
471 config.selectionModel = config.selModel;\r
472 }\r
473 this.callParent([config]);\r
474 },\r
475\r
476 initComponent: function(){\r
477 var me = this,\r
478 isDef = Ext.isDefined,\r
479 itemTpl = me.itemTpl,\r
480 memberFn = {},\r
481 store;\r
482\r
483 if (itemTpl) {\r
484 if (Ext.isArray(itemTpl)) {\r
485 // string array\r
486 if (typeof itemTpl[itemTpl.length - 1] !== 'string') {\r
487 itemTpl = itemTpl.slice(0);\r
488 memberFn = itemTpl.pop();\r
489 }\r
490 itemTpl = itemTpl.join('');\r
491 } else if (Ext.isObject(itemTpl)) {\r
492 // tpl instance\r
493 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);\r
494 itemTpl = itemTpl.html;\r
495 }\r
496\r
497 if (!me.itemSelector) {\r
498 me.itemSelector = '.' + me.itemCls;\r
499 }\r
500\r
501 itemTpl = Ext.String.format('<tpl for="."><div class="{0}" role="{2}">{1}</div></tpl>', me.itemCls, itemTpl, me.itemAriaRole);\r
502 me.tpl = new Ext.XTemplate(itemTpl, memberFn);\r
503 }\r
504\r
505 //<debug>\r
506 if (!isDef(me.tpl) || !isDef(me.itemSelector)) {\r
507 Ext.raise({\r
508 sourceClass: 'Ext.view.View',\r
509 tpl: me.tpl,\r
510 itemSelector: me.itemSelector,\r
511 msg: "DataView requires both tpl and itemSelector configurations to be defined."\r
512 });\r
513 }\r
514 //</debug>\r
515\r
516 me.callParent();\r
517 me.tpl = me.getTpl('tpl');\r
518\r
519 //<debug>\r
520 // backwards compat alias for overClass/selectedClass\r
521 // TODO: Consider support for overCls generation Ext.Component config\r
522 if (isDef(me.overCls) || isDef(me.overClass)) {\r
523 if (Ext.isDefined(Ext.global.console)) {\r
524 Ext.global.console.warn('Ext.view.View: Using the deprecated overCls or overClass configuration. Use overItemCls instead.');\r
525 }\r
526 me.overItemCls = me.overCls || me.overClass;\r
527 delete me.overCls;\r
528 delete me.overClass;\r
529 }\r
530\r
531 if (isDef(me.selectedCls) || isDef(me.selectedClass)) {\r
532 if (Ext.isDefined(Ext.global.console)) {\r
533 Ext.global.console.warn('Ext.view.View: Using the deprecated selectedCls or selectedClass configuration. Use selectedItemCls instead.');\r
534 }\r
535 me.selectedItemCls = me.selectedCls || me.selectedClass;\r
536 delete me.selectedCls;\r
537 delete me.selectedClass;\r
538 }\r
539 //</debug>\r
540\r
541 if (me.overItemCls) {\r
542 me.trackOver = true;\r
543 }\r
544\r
545 me.addCmpEvents();\r
546\r
547 // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.\r
548 store = me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');\r
549\r
550 // Use the provided store as the data source unless a Feature or plugin has injected a special one\r
551 if (!me.dataSource) {\r
552 me.dataSource = store;\r
553 }\r
554\r
555 me.bindStore(store, true);\r
556\r
557 // Must exist before the selection model.\r
558 // Selection model listens to this for navigation events.\r
559 me.getNavigationModel().bindComponent(this);\r
560\r
561 if (!me.all) {\r
562 me.all = new Ext.CompositeElementLite();\r
563 }\r
564\r
565 // We track the scroll position\r
566 me.scrollState = {\r
567 top: 0,\r
568 left: 0\r
569 };\r
570\r
571 me.savedTabIndexAttribute = 'data-savedtabindex-' + me.id;\r
572 },\r
573\r
574 getElConfig: function() {\r
575 var result = this.mixins.renderable.getElConfig.call(this);\r
576\r
577 // Subclasses may set focusable to false (BoundList is not focusable)\r
578 if (this.focusable) {\r
579 result.tabIndex = 0;\r
580 }\r
581 return result;\r
582 },\r
583\r
584 onRender: function() {\r
585 var mask = this.loadMask;\r
586\r
587 this.callParent(arguments);\r
588 if (mask) {\r
589 this.createMask(mask);\r
590 }\r
591 },\r
592\r
593 beforeLayout: function() {\r
594 var me = this;\r
595\r
596 me.callParent(arguments);\r
597\r
598 // If a refresh is needed, just before the layout is the time to apply it.\r
599 // If there is a deferred refresh timer running, allow that to do the refresh.\r
600 if (me.refreshNeeded && !me.pendingRefresh) {\r
601 // If we have refreshed before, just call a refresh now.\r
602 if (me.refreshCounter) {\r
603 me.refresh();\r
604 }\r
605 else {\r
606 me.doFirstRefresh(me.dataSource);\r
607 }\r
608 }\r
609 },\r
610\r
611 onMaskBeforeShow: function(){\r
612 var me = this,\r
613 loadingHeight = me.loadingHeight;\r
614\r
615 if (loadingHeight && loadingHeight > me.getHeight()) {\r
616 me.hasLoadingHeight = true;\r
617 me.oldMinHeight = me.minHeight;\r
618 me.minHeight = loadingHeight;\r
619 me.updateLayout();\r
620 }\r
621 },\r
622\r
623 onMaskHide: function(){\r
624 var me = this;\r
625\r
626 if (!me.destroying && me.hasLoadingHeight) {\r
627 me.minHeight = me.oldMinHeight;\r
628 me.updateLayout();\r
629 delete me.hasLoadingHeight;\r
630 }\r
631 },\r
632\r
633 beforeRender: function() {\r
634 this.callParent(arguments);\r
635 this.getSelectionModel().beforeViewRender(this);\r
636 },\r
637\r
638 afterRender: function() {\r
639 this.callParent(arguments);\r
640\r
641 // Subclasses may set focusable to false.\r
642 // BoundList is not focusable.\r
643 // BoundList processes key events from its boundField.\r
644 if (this.focusable) {\r
645 this.focusEl = this.el;\r
646 }\r
647 },\r
648\r
649 getRefItems: function() {\r
650 var mask = this.loadMask,\r
651 result = [];\r
652\r
653 if (mask && mask.isComponent) {\r
654 result.push(mask);\r
655 }\r
656 return result;\r
657 },\r
658\r
659 getSelection: function() {\r
660 return this.getSelectionModel().getSelection();\r
661 },\r
662\r
663 updateSelection: function(selection) {\r
664 var me = this,\r
665 sm;\r
666\r
667 if (!me.ignoreNextSelection) {\r
668 me.ignoreNextSelection = true;\r
669 sm = me.getSelectionModel();\r
670 if (selection) {\r
671 sm.select(selection);\r
672 } else {\r
673 sm.deselectAll();\r
674 }\r
675 me.ignoreNextSelection = false;\r
676 }\r
677 },\r
678\r
679 updateBindSelection: function(selModel, selection) {\r
680 var me = this,\r
681 selected = null;\r
682\r
683 if (!me.ignoreNextSelection) {\r
684 me.ignoreNextSelection = true;\r
685 if (selection.length) {\r
686 selected = selModel.getLastSelected();\r
687 me.hasHadSelection = true;\r
688 }\r
689 if (me.hasHadSelection) {\r
690 me.setSelection(selected);\r
691 }\r
692 me.ignoreNextSelection = false;\r
693 }\r
694 },\r
695\r
696 applySelectionModel: function(selModel, oldSelModel) { \r
697 var me = this,\r
698 grid = me.grid,\r
699 mode, ariaAttr, ariaDom;\r
700\r
701 if (oldSelModel) {\r
702 oldSelModel.un({\r
703 scope: me,\r
704 selectionchange: me.updateBindSelection,\r
705 lastselectedchanged: me.updateBindSelection,\r
706 select: me.ariaSelect,\r
707 deselect: me.ariaDeselect\r
708 });\r
709 \r
710 Ext.destroy(me.selModelRelayer);\r
711 selModel = Ext.Factory.selection(selModel);\r
712 }\r
713 // If this is the initial configuration, pull overriding configs in from this view\r
714 else {\r
715 if (selModel && selModel.isSelectionModel) {\r
716 selModel.locked = me.disableSelection;\r
717 } else {\r
718 if (me.simpleSelect) {\r
719 mode = 'SIMPLE';\r
720 } else if (me.multiSelect) {\r
721 mode = 'MULTI';\r
722 } else {\r
723 mode = 'SINGLE';\r
724 }\r
725\r
726 if (typeof selModel === 'string') {\r
727 selModel = {\r
728 type: selModel\r
729 };\r
730 }\r
731 selModel = Ext.Factory.selection(Ext.apply({\r
732 allowDeselect: me.allowDeselect || me.multiSelect,\r
733 mode: mode,\r
734 locked: me.disableSelection\r
735 }, selModel));\r
736 }\r
737 }\r
738 \r
739 // Grids should have aria-multiselectable on their ariaEl instead\r
740 if (selModel.mode !== 'SINGLE') {\r
741 ariaDom = (grid || me).ariaEl.dom;\r
742 \r
743 if (ariaDom) {\r
744 ariaDom.setAttribute('aria-multiselectable', true);\r
745 }\r
746 else if (!grid) {\r
747 ariaAttr = me.ariaRenderAttributes || (me.ariaRenderAttributes = {});\r
748 ariaAttr['aria-multiselectable'] = true;\r
749 }\r
750 }\r
751\r
752 me.selModelRelayer = me.relayEvents(selModel, [\r
753 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect', 'focuschange'\r
754 ]);\r
755 \r
756 selModel.on({\r
757 scope: me,\r
758 lastselectedchanged: me.updateBindSelection,\r
759 selectionchange: me.updateBindSelection,\r
760 select: me.ariaSelect,\r
761 deselect: me.ariaDeselect\r
762 });\r
763\r
764 return selModel;\r
765 },\r
766\r
767 updateSelectionModel: function(selectionModel) {\r
768 // Keep the legacy property correct\r
769 this.selModel = selectionModel;\r
770 },\r
771\r
772 applyNavigationModel: function (navigationModel) {\r
773 return Ext.Factory.viewNavigation(navigationModel);\r
774 },\r
775\r
776 onFocusEnter: function(e) {\r
777 var me = this,\r
778 navigationModel = me.getNavigationModel(),\r
779 focusPosition;\r
780\r
781 // Disable tabbability of elements within this view.\r
782 me.toggleChildrenTabbability(false);\r
783 \r
784 if (!me.itemFocused && me.all.getCount()) {\r
785 focusPosition = navigationModel.getLastFocused();\r
786 navigationModel.setPosition(focusPosition || 0, e.event, null, !focusPosition);\r
787\r
788 // We now contain focus is that was successful\r
789 me.itemFocused = navigationModel.getPosition() != null;\r
790 }\r
791 \r
792 // View's main el should be kept untabbable, otherwise pressing\r
793 // Shift-Tab key in the view would move the focus to the main el\r
794 // which will then bounce it back to the last focused item.\r
795 // That would effectively make Shift-Tab unusable.\r
796 if (me.itemFocused) {\r
797 this.el.dom.setAttribute('tabIndex', '-1');\r
798 }\r
799\r
800 me.callParent([e]);\r
801 },\r
802\r
803 onFocusLeave: function(e) {\r
804 var me = this;\r
805\r
806 // Ignore this event if we do not actually contain focus,\r
807 // or if the reason for focus exiting was that we are refreshing.\r
808 if (me.itemFocused && !me.refreshing) {\r
809\r
810 // Blur the focused cell\r
811 me.getNavigationModel().setPosition(null, e.event, null, true);\r
812\r
813 me.itemFocused = false;\r
814 me.el.dom.setAttribute('tabIndex', 0);\r
815 }\r
816\r
817 me.callParent([e]);\r
818 },\r
819 \r
820 ariaSelect: function(selModel, record) {\r
821 var node = this.getNode(record);\r
822 \r
823 if (node) {\r
824 node.setAttribute('aria-selected', true);\r
825 }\r
826 },\r
827 \r
828 ariaDeselect: function(selModel, record) {\r
829 var node = this.getNode(record);\r
830 \r
831 if (node) {\r
832 node.removeAttribute('aria-selected');\r
833 }\r
834 },\r
835\r
836 onRemoved: function(isDestroying) {\r
837 this.callParent([isDestroying]);\r
838\r
839 // IE does not fire focusleave on removal from DOM\r
840 if (!isDestroying) {\r
841 this.onFocusLeave({});\r
842 }\r
843 },\r
844 \r
845 /**\r
846 * Refreshes the view by reloading the data from the store and re-rendering the template.\r
847 * @since 2.3.0\r
848 */\r
849 refresh: function() {\r
850 var me = this,\r
851 items = me.all,\r
852 prevItemCount = items.getCount(),\r
853 refreshCounter = me.refreshCounter,\r
854 targetEl,\r
855 dom,\r
856 records,\r
857 selModel = me.getSelectionModel(),\r
858 restoreFocus,\r
859 // If there are items in the view, then honour preserveScrollOnRefresh\r
860 scroller = refreshCounter && items.getCount() && me.preserveScrollOnRefresh && me.getScrollable(),\r
861 scrollPos;\r
862\r
863 if (!me.rendered || me.destroyed) {\r
864 return;\r
865 }\r
866\r
867 if (!me.hasListeners.beforerefresh || me.fireEvent('beforerefresh', me) !== false) {\r
868\r
869 // So that listeners to itemremove events know that its because of a refresh\r
870 me.refreshing = true;\r
871\r
872 // If focus was in this view, this will restore it\r
873 restoreFocus = me.saveFocusState();\r
874\r
875 targetEl = me.getTargetEl();\r
876 records = me.getViewRange();\r
877 dom = targetEl.dom;\r
878\r
879 if (scroller) {\r
880 scrollPos = scroller.getPosition();\r
881 if (!(scrollPos.x || scrollPos.y)) {\r
882 scrollPos = null;\r
883 }\r
884 }\r
885\r
886 if (refreshCounter) {\r
887 me.clearViewEl();\r
888 me.refreshCounter++;\r
889 } else {\r
890 me.refreshCounter = 1;\r
891 }\r
892\r
893 // Usually, for an empty record set, this would be blank, but when the Template\r
894 // Creates markup outside of the record loop, this must still be honoured even if there are no\r
895 // records.\r
896 me.tpl.append(targetEl, me.collectData(records, items.startIndex || 0));\r
897\r
898 // The emptyText is now appended to the View's element\r
899 // after nodes outside the tpl block.\r
900 if (records.length < 1) {\r
901 // Process empty text unless the store is being cleared.\r
902 me.addEmptyText();\r
903 items.clear();\r
904 } else {\r
905 me.collectNodes(targetEl.dom);\r
906 me.updateIndexes(0);\r
907 }\r
908\r
909 // If focus was in any way in this view, this will restore it\r
910 restoreFocus();\r
911\r
912 // Some subclasses do not need to do this. TableView does not need to do this - it renders selected class using its tenmplate.\r
913 if (me.refreshSelmodelOnRefresh !== false) {\r
914 selModel.refresh();\r
915 }\r
916\r
917 me.refreshNeeded = false;\r
918\r
919 // Ensure layout system knows about new content size.\r
920 // If number of items have changed, force a layout.\r
921 me.refreshSize(items.getCount() !== prevItemCount);\r
922\r
923 me.fireEvent('refresh', me, records);\r
924\r
925 if (scroller) {\r
926 scroller.scrollTo(scrollPos);\r
927 }\r
928\r
929 // Upon first refresh, fire the viewready event.\r
930 // Reconfiguring the grid "renews" this event.\r
931 if (!me.viewReady) {\r
932 // Fire an event when deferred content becomes available.\r
933 me.viewReady = true;\r
934 me.fireEvent('viewready', me);\r
935 }\r
936 \r
937 me.refreshing = false;\r
938 me.refreshScroll();\r
939 \r
940 me.cleanupData();\r
941 }\r
942 },\r
943\r
944 addEmptyText: function() { \r
945 var me = this,\r
946 store = me.getStore();\r
947\r
948 if (me.emptyText && !store.isLoading() && (!me.deferEmptyText || me.refreshCounter > 1 || store.isLoaded())) {\r
949 me.emptyEl = Ext.core.DomHelper.insertHtml('beforeEnd', me.getTargetEl().dom, me.emptyText);\r
950 }\r
951 },\r
952\r
953 getViewRange: function() {\r
954 return this.dataSource.getRange();\r
955 },\r
956\r
957 /**\r
958 * @private\r
959 * Called by the framework when the view is refreshed, or when rows are added or deleted.\r
960 *\r
961 * These operations may cause the view's dimensions to change, and if the owning container\r
962 * is shrinkwrapping this view, then the layout must be updated to accommodate these new dimensions.\r
963 */\r
964 refreshSize: function(forceLayout) {\r
965 var me = this,\r
966 sizeModel = me.getSizeModel(),\r
967 scroller = me.getScrollable();\r
968\r
969 if (sizeModel.height.shrinkWrap || sizeModel.width.shrinkWrap || forceLayout) {\r
970 me.updateLayout();\r
971 }\r
972\r
973 // We need to refresh the Scroller (BufferedRenderer has to do this if present).\r
974 // But the first refresh takes place on the leading edge of the first layout\r
975 // before the Scroller has been initialized, so do it as soon\r
976 // as we reach boxready.\r
977 else if (me.touchScroll && !me.bufferedRenderer) {\r
978 if (scroller) {\r
979 scroller.refresh();\r
980 } else {\r
981 me.on({\r
982 boxready: me.refreshScroll,\r
983 scope: me,\r
984 single: true\r
985 });\r
986 }\r
987 }\r
988 },\r
989\r
990 afterFirstLayout: function(width, height) {\r
991 var me = this,\r
992 scroller = me.getScrollable();\r
993\r
994 if (scroller) {\r
995 scroller.on({\r
996 scroll: me.onViewScroll,\r
997 scrollend: me.onViewScrollEnd,\r
998 scope: me,\r
999 onFrame: !!Ext.global.requestAnimationFrame\r
1000 });\r
1001 }\r
1002 me.callParent([width, height]);\r
1003 },\r
1004\r
1005 clearViewEl: function() {\r
1006 var me = this,\r
1007 targetEl = me.getTargetEl(),\r
1008 nodeContainerIsTarget = me.getNodeContainer() === targetEl;\r
1009\r
1010 me.clearEmptyEl();\r
1011 // If nodeContainer is the el, just clear the innerHTML. Otherwise, we need\r
1012 // to manually remove each node we know about.\r
1013 me.all.clear(!nodeContainerIsTarget);\r
1014 if (nodeContainerIsTarget) {\r
1015 targetEl.dom.innerHTML = '';\r
1016 }\r
1017 },\r
1018\r
1019 clearEmptyEl: function() {\r
1020 var emptyEl = this.emptyEl;\r
1021\r
1022 // emptyEl is likely to be a TextNode if emptyText is not HTML code.\r
1023 // Use native DOM to remove it.\r
1024 if (emptyEl) {\r
1025 Ext.removeNode(emptyEl);\r
1026 }\r
1027 this.emptyEl = null;\r
1028 },\r
1029\r
1030 onViewScroll: function(scroller, x, y) {\r
1031 this.fireEvent('scroll', this, x, y);\r
1032 },\r
1033\r
1034 onViewScrollEnd: function(scroller, x, y) {\r
1035 this.fireEvent('scrollend', this, x, y);\r
1036 },\r
1037\r
1038 /**\r
1039 * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState.\r
1040 * @private\r
1041 */\r
1042 saveScrollState: function() {\r
1043 var me = this,\r
1044 state = me.scrollState;\r
1045\r
1046 if (me.rendered) {\r
1047 state.left = me.getScrollX();\r
1048 state.top = me.getScrollY();\r
1049 }\r
1050 },\r
1051\r
1052 /**\r
1053 * Restores the scrollState.\r
1054 * Must be used in conjunction with saveScrollState\r
1055 * @private\r
1056 */\r
1057 restoreScrollState: function() {\r
1058 var me = this,\r
1059 state = me.scrollState;\r
1060\r
1061 if (me.rendered) {\r
1062 me.setScrollX(state.left);\r
1063 me.setScrollY(state.top);\r
1064 }\r
1065 },\r
1066\r
1067 /**\r
1068 * Function which can be overridden to provide custom formatting for each Record that is used by this\r
1069 * DataView's {@link #tpl template} to render each node.\r
1070 * @param {Object/Object[]} data The raw data object that was used to create the Record.\r
1071 * @param {Number} recordIndex the index number of the Record being prepared for rendering.\r
1072 * @param {Ext.data.Model} record The Record being prepared for rendering.\r
1073 * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.\r
1074 * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))\r
1075 * @since 2.3.0\r
1076 */\r
1077 prepareData: function(data, index, record) {\r
1078 var associatedData, attr, hasCopied;\r
1079 if (record) {\r
1080 associatedData = record.getAssociatedData();\r
1081 for (attr in associatedData) {\r
1082 if (associatedData.hasOwnProperty(attr)) {\r
1083 // This would be better done in collectData, however\r
1084 // we only need to copy the data object if we have any associations,\r
1085 // so we optimize it by only copying if we must.\r
1086 // We do this so we don't mutate the underlying record.data\r
1087 if (!hasCopied) {\r
1088 data = Ext.Object.chain(data);\r
1089 hasCopied = true;\r
1090 }\r
1091 data[attr] = associatedData[attr];\r
1092 }\r
1093 }\r
1094 }\r
1095 return data;\r
1096 },\r
1097\r
1098 /**\r
1099 * Function which can be overridden which returns the data object passed to this\r
1100 * DataView's {@link #cfg-tpl template} to render the whole DataView.\r
1101 *\r
1102 * This is usually an Array of data objects, each element of which is processed by an\r
1103 * {@link Ext.XTemplate XTemplate} which uses `'&lt;tpl for="."&gt;'` to iterate over its supplied\r
1104 * data object as an Array. However, <i>named</i> properties may be placed into the data object to\r
1105 * provide non-repeating data such as headings, totals etc.\r
1106 *\r
1107 * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.\r
1108 * @param {Number} startIndex the index number of the Record being prepared for rendering.\r
1109 * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also\r
1110 * contain <i>named</i> properties.\r
1111 * @since 2.3.0\r
1112 */\r
1113 collectData: function(records, startIndex){\r
1114 var data = [],\r
1115 i = 0,\r
1116 len = records.length,\r
1117 record;\r
1118\r
1119 for (; i < len; i++) {\r
1120 record = records[i];\r
1121 data[i] = this.prepareData(record.data, startIndex + i, record);\r
1122 }\r
1123 return data;\r
1124 },\r
1125 \r
1126 cleanupData: Ext.emptyFn,\r
1127\r
1128 bufferRender: function(records, index) {\r
1129 var me = this,\r
1130 div = me.renderBuffer,\r
1131 result = document.createDocumentFragment(),\r
1132 nodes, len, i;\r
1133\r
1134 me.tpl.overwrite(div, me.collectData(records, index));\r
1135 nodes = Ext.fly(div).query(me.getItemSelector());\r
1136 for (i = 0, len = nodes.length; i < len; i++) {\r
1137 result.appendChild(nodes[i]);\r
1138 }\r
1139 return {\r
1140 fragment: result,\r
1141 children: nodes\r
1142 };\r
1143 },\r
1144\r
1145 // Element which contains rows\r
1146 nodeContainerSelector: null,\r
1147\r
1148 getNodeContainer: function() {\r
1149 var target = this.getTargetEl(),\r
1150 selector = this.nodeContainerSelector;\r
1151 return selector ? target.down(selector, true) : target;\r
1152 },\r
1153\r
1154 /**\r
1155 * Returns a CSS selector which selects the element which contains record nodes.\r
1156 */\r
1157 getNodeContainerSelector: function() {\r
1158 return this.nodeContainerSelector;\r
1159 },\r
1160\r
1161 onUpdate: function(store, record, operation, modifiedFieldNames, details) {\r
1162 var me = this,\r
1163 isFiltered = details && details.filtered;\r
1164\r
1165 // If, due to filtering or buffered rendering, or node collapse, the updated record is not\r
1166 // represented in the rendered structure, this is a no-op.\r
1167 // The correct, new values will be rendered the next time the record becomes visible and is rendered.\r
1168 if (!isFiltered && me.getNode(record)) {\r
1169\r
1170 // If we are throttling UI updates (See the updateDelay global config), ensure there's a change entry\r
1171 // queued for the record in the global queue.\r
1172 if (me.throttledUpdate) {\r
1173 me.statics().queueRecordChange(me, store, record, operation, modifiedFieldNames);\r
1174 } else {\r
1175 me.handleUpdate.apply(me, arguments);\r
1176 }\r
1177 }\r
1178 },\r
1179\r
1180 handleUpdate: function(store, record){\r
1181 var me = this,\r
1182 index,\r
1183 node,\r
1184 selModel = me.getSelectionModel();\r
1185\r
1186 if (me.viewReady) {\r
1187 index = me.dataSource.indexOf(record);\r
1188\r
1189 // If the record has been removed from the data source since the changes were made, do nothing\r
1190 if (index > -1) {\r
1191 // ensure the node actually exists in the DOM\r
1192 if (me.getNode(record)) {\r
1193 node = me.bufferRender([record], index).children[0];\r
1194 me.all.replaceElement(index, node, true);\r
1195 me.updateIndexes(index, index);\r
1196 // Maintain selection after update\r
1197 selModel.onUpdate(record);\r
1198 me.refreshSizePending = true;\r
1199 if (selModel.isSelected(record)) {\r
1200 me.onItemSelect(record);\r
1201 }\r
1202 if (me.hasListeners.itemupdate) {\r
1203 me.fireEvent('itemupdate', record, index, node);\r
1204 }\r
1205 return node;\r
1206 }\r
1207 }\r
1208 }\r
1209 },\r
1210\r
1211 /**\r
1212 * @private\r
1213 * Respond to store replace event which is fired by GroupStore group expand/collapse operations.\r
1214 * This saves a layout because a remove and add operation are coalesced in this operation.\r
1215 */\r
1216 onReplace: function(store, startIndex, oldRecords, newRecords) {\r
1217 var me = this,\r
1218 all = me.all,\r
1219 selModel = me.getSelectionModel(),\r
1220 origStart = startIndex,\r
1221 result, item, fragment, children, oldItems, endIndex, restoreFocus;\r
1222\r
1223 if (me.rendered) {\r
1224 // Insert the new items before the remove block\r
1225 result = me.bufferRender(newRecords, startIndex, true);\r
1226 fragment = result.fragment;\r
1227 children = result.children;\r
1228 item = all.item(startIndex);\r
1229\r
1230 if (item) {\r
1231 all.item(startIndex).insertSibling(fragment, 'before', true);\r
1232 } else {\r
1233 me.appendNodes(fragment);\r
1234 }\r
1235\r
1236 all.insert(startIndex, children);\r
1237\r
1238 if (oldRecords.length) {\r
1239 // If focus was in the view, this will return\r
1240 // a function which will restore that state.\r
1241 // If not, a function which does nothing.\r
1242 restoreFocus = me.saveFocusState();\r
1243 }\r
1244\r
1245 startIndex += newRecords.length;\r
1246 endIndex = startIndex + oldRecords.length - 1;\r
1247\r
1248 // Remove the items which correspond to old records\r
1249 oldItems = all.removeRange(startIndex, endIndex, true);\r
1250\r
1251 // Some subclasses do not need to do this. TableView does not need to do this.\r
1252 if (me.refreshSelmodelOnRefresh !== false) {\r
1253 selModel.refresh();\r
1254 }\r
1255\r
1256 // Update the row indices (TableView) doesn't do this.\r
1257 me.updateIndexes(startIndex);\r
1258\r
1259 if (me.hasListeners.itemremove) {\r
1260 me.fireEvent('itemremove', oldRecords, origStart, oldItems, me);\r
1261 }\r
1262\r
1263 if (me.hasListeners.itemadd) {\r
1264 me.fireEvent('itemadd', newRecords, origStart, children);\r
1265 }\r
1266\r
1267 // If focus was in this view, this will restore it\r
1268 restoreFocus();\r
1269\r
1270 me.refreshSize();\r
1271 }\r
1272 },\r
1273\r
1274 onAdd: function(store, records, index) {\r
1275 var me = this,\r
1276 nodes,\r
1277 selModel = me.getSelectionModel();\r
1278\r
1279 if (me.rendered) {\r
1280 // If we are adding into an empty view, we must refresh in order that the *full tpl* is applied\r
1281 // which might create boilerplate content *around* the record nodes.\r
1282 if (me.all.getCount() === 0) {\r
1283 me.refresh();\r
1284 nodes = me.all.slice();\r
1285 } else {\r
1286 nodes = me.doAdd(records, index);\r
1287 // Some subclasses do not need to do this. TableView does not need to do this.\r
1288 if (me.refreshSelmodelOnRefresh !== false) {\r
1289 selModel.refresh();\r
1290 }\r
1291 me.updateIndexes(index);\r
1292\r
1293 // Ensure layout system knows about new content size\r
1294 me.refreshSizePending = true;\r
1295 }\r
1296\r
1297 if (me.hasListeners.itemadd) {\r
1298 me.fireEvent('itemadd', records, index, nodes);\r
1299 }\r
1300 }\r
1301\r
1302 },\r
1303 \r
1304 appendNodes: function(nodes) {\r
1305 var all = this.all,\r
1306 count = all.getCount();\r
1307\r
1308 if (this.nodeContainerSelector) {\r
1309 this.getNodeContainer().appendChild(nodes);\r
1310 } else {\r
1311 // If we don't have a nodeContainerSelector, we may have our\r
1312 // itemSelector nodes wrapped in some other container, so we\r
1313 // can't just append them to the node container, it may be the wrong element\r
1314 all.item(count - 1).insertSibling(nodes, 'after');\r
1315 }\r
1316 },\r
1317\r
1318 doAdd: function(records, index) {\r
1319 var me = this,\r
1320 result = me.bufferRender(records, index, true),\r
1321 fragment = result.fragment,\r
1322 children = result.children,\r
1323 all = me.all,\r
1324 count = all.getCount(),\r
1325 firstRowIndex = all.startIndex || 0,\r
1326\r
1327 lastRowIndex = all.endIndex || count - 1;\r
1328\r
1329 if (count === 0 || index > lastRowIndex) {\r
1330 me.appendNodes(fragment);\r
1331 } else if (index <= firstRowIndex) {\r
1332 all.item(firstRowIndex).insertSibling(fragment, 'before', true);\r
1333 } else {\r
1334 all.item(index).insertSibling(children, 'before', true);\r
1335 }\r
1336\r
1337 all.insert(index, children);\r
1338 return children;\r
1339 },\r
1340\r
1341 onRemove: function(store, records, index) {\r
1342 var me = this,\r
1343 rows = me.all,\r
1344 fireItemRemove = me.hasListeners.itemremove,\r
1345 currIdx, i, record, nodes, node, restoreFocus;\r
1346\r
1347 if (rows.getCount()) {\r
1348 if (me.dataSource.getCount() === 0) {\r
1349 // Refresh so emptyText can be applied if necessary\r
1350 if (fireItemRemove) {\r
1351 me.fireEvent('itemremove', records, index, me.getNodes(index, index + records.length - 1));\r
1352 }\r
1353 me.refresh();\r
1354 } else {\r
1355 // If this view contains focus, this will return\r
1356 // a function which will restore that state.\r
1357 restoreFocus = me.saveFocusState();\r
1358\r
1359 // Just remove the elements which corresponds to the removed records\r
1360 // The tpl's full HTML will still be in place.\r
1361 if (fireItemRemove) {\r
1362 nodes = [];\r
1363 }\r
1364 for (i = records.length - 1; i >= 0; --i) {\r
1365 record = records[i];\r
1366 currIdx = index + i;\r
1367 if (nodes) {\r
1368 node = rows.item(currIdx);\r
1369 nodes[i] = node ? node.dom : undefined;\r
1370 }\r
1371 \r
1372 if (rows.item(currIdx)) {\r
1373 me.doRemove(record, currIdx);\r
1374 }\r
1375 }\r
1376\r
1377 if (fireItemRemove) {\r
1378 me.fireEvent('itemremove', records, index, nodes, me);\r
1379 }\r
1380\r
1381 // If focus was in this view, this will restore it\r
1382 restoreFocus();\r
1383 me.updateIndexes(index);\r
1384 }\r
1385\r
1386 // Ensure layout system knows about new content size\r
1387 me.refreshSizePending = true;\r
1388 }\r
1389 },\r
1390\r
1391 doRemove: function(record, index) {\r
1392 this.all.removeElement(index, true);\r
1393 },\r
1394 \r
1395 /**\r
1396 * @private\r
1397 * Called prior to an operation which mey remove focus from this view by some kind of DOM operation.\r
1398 *\r
1399 * If this view contains focus, this method returns a function which, when called after\r
1400 * the disruptive DOM operation will restore focus to the same record, or, if the record has\r
1401 * been removed to the same item index..\r
1402 *\r
1403 * @returns {Function} A function that will restore focus if focus was within this view,\r
1404 * or a function which does nothing is focus is not in this view.\r
1405 */\r
1406 saveFocusState: function() {\r
1407 var me = this,\r
1408 store = me.dataSource || me.store,\r
1409 navModel = me.getNavigationModel(),\r
1410 lastFocusedIndex = navModel.recordIndex,\r
1411 lastFocusedRec = navModel.record;\r
1412\r
1413 // Check if we really have focus.\r
1414 // Some NavigationModels record position with focus outside of the view.\r
1415 // This happens in BoundLists when focus stays in the bound field.\r
1416 if (me.el.contains(Ext.Element.getActiveElement())) {\r
1417 // Blur the focused descendant, but do not trigger focusLeave.\r
1418 me.el.dom.focus();\r
1419\r
1420 // The following function will attempt to refocus back to the same record if it is still there,\r
1421 // or the same item index.\r
1422 return function() {\r
1423 // If we still have data, attempt to refocus at the same record, or the same item index..\r
1424 if (store.getCount()) {\r
1425\r
1426 // Adjust expectations of where we are able to refocus according to what kind of destruction\r
1427 // might have been wrought on this view's DOM during focus save.\r
1428 lastFocusedIndex = Math.min(lastFocusedIndex, me.all.getCount() - 1);\r
1429 navModel.setPosition(store.contains(lastFocusedRec) ? lastFocusedRec : lastFocusedIndex, null, null, true);\r
1430 }\r
1431 };\r
1432 }\r
1433 return Ext.emptyFn;\r
1434 },\r
1435\r
1436 /**\r
1437 * Refreshes an individual node's data from the store.\r
1438 * @param {Ext.data.Model/Number} record The record or index of the record to update.\r
1439 * @since 2.3.0\r
1440 */\r
1441 refreshNode: function(record) {\r
1442 if (Ext.isNumber(record)) {\r
1443 record = this.store.getAt(record);\r
1444 }\r
1445 this.onUpdate(this.dataSource, record);\r
1446 },\r
1447\r
1448 updateIndexes: function(startIndex, endIndex) {\r
1449 var nodes = this.all.elements,\r
1450 node,\r
1451 records = this.getViewRange(),\r
1452 i,\r
1453 myId = this.id;\r
1454\r
1455 startIndex = startIndex || 0;\r
1456 endIndex = endIndex || ((endIndex === 0) ? 0 : (nodes.length - 1));\r
1457 for (i = startIndex; i <= endIndex; i++) {\r
1458 node = nodes[i];\r
1459 node.setAttribute('data-recordIndex', i);\r
1460 node.setAttribute('data-recordId', records[i].internalId);\r
1461 node.setAttribute('data-boundView', myId);\r
1462 }\r
1463 },\r
1464\r
1465 /**\r
1466 * Changes the data store bound to this view and refreshes it.\r
1467 * @param {Ext.data.Store} store The store to bind to this view\r
1468 * @since 3.4.0\r
1469 */\r
1470 bindStore: function(store, initial) {\r
1471 var me = this,\r
1472 selModel = me.getSelectionModel(),\r
1473 navModel = me.getNavigationModel();\r
1474\r
1475 selModel.bindStore(store);\r
1476 selModel.bindComponent(store ? me : null);\r
1477 me.mixins.storeholder.bindStore.apply(me, arguments);\r
1478\r
1479 // Navigation model must bind to new store\r
1480 navModel.setStore(store);\r
1481\r
1482 // If we have already achieved our first layout, refresh immediately.\r
1483 // If we bind to the Store before the first layout, then beforeLayout will\r
1484 // call doFirstRefresh\r
1485 if (store && me.componentLayoutCounter) {\r
1486 // If not the initial bind, we enforce noDefer.\r
1487 me.doFirstRefresh(store, !initial);\r
1488 }\r
1489 },\r
1490\r
1491 /**\r
1492 * @private\r
1493 * Perform the first refresh of the View from a newly bound store.\r
1494 *\r
1495 * This is called when this View has been sized for the first time.\r
1496 */\r
1497 doFirstRefresh: function(store, noDefer) {\r
1498 var me = this;\r
1499\r
1500 // If we are configured to defer, and *NOT* called from the defer call below\r
1501 if (me.deferInitialRefresh && !noDefer) {\r
1502 Ext.defer(me.doFirstRefresh, 1, me, [store, true]);\r
1503 }\r
1504\r
1505 else {\r
1506 // 4.1.0: If we have a store, and the Store is *NOT* already loading (a refresh is on the way), then\r
1507 // on first layout, refresh regardless of record count.\r
1508 // Template may contain boilerplate HTML outside of record iteration loop.\r
1509 // Also, emptyText is appended by the refresh method.\r
1510 if (store && !store.isLoading()) {\r
1511 me.refresh();\r
1512 }\r
1513 }\r
1514 },\r
1515\r
1516 onUnbindStore: function(store) {\r
1517 this.setMaskBind(null);\r
1518\r
1519 if (this.dataSource === store) {\r
1520 this.dataSource = null;\r
1521 }\r
1522 },\r
1523\r
1524 onBindStore: function(store, oldStore) {\r
1525 var me = this;\r
1526\r
1527 // A BufferedStore has to know to reload the most recent visible zone if its View is preserveScrollOnReload\r
1528 if (me.store.isBufferedStore) {\r
1529 me.store.preserveScrollOnReload = me.preserveScrollOnReload;\r
1530 }\r
1531 if (oldStore && oldStore.isBufferedStore) {\r
1532 delete oldStore.preserveScrollOnReload;\r
1533 }\r
1534\r
1535 me.setMaskBind(store);\r
1536\r
1537 // When unbinding the data store, the dataSource will be nulled out if it's the same as the data store.\r
1538 // Restore it here.\r
1539 if (!me.dataSource) {\r
1540 me.dataSource = store;\r
1541 }\r
1542 },\r
1543\r
1544 setMaskBind: function(store) {\r
1545 var mask = this.loadMask;\r
1546\r
1547\r
1548 if (this.rendered && mask && store && !mask.bindStore) {\r
1549 mask = this.createMask();\r
1550 }\r
1551\r
1552 if (mask && mask.bindStore) {\r
1553 mask.bindStore(store);\r
1554 }\r
1555 },\r
1556\r
1557 getStoreListeners: function() {\r
1558 var me = this;\r
1559 return {\r
1560 refresh: me.onDataRefresh,\r
1561 replace: me.onReplace,\r
1562 add: me.onAdd,\r
1563 remove: me.onRemove,\r
1564 update: me.onUpdate,\r
1565 clear: me.onDataRefresh,\r
1566 beginupdate: me.onBeginUpdate,\r
1567 endupdate: me.onEndUpdate\r
1568 };\r
1569 },\r
1570\r
1571 onBeginUpdate: function() {\r
1572 ++this.updateSuspendCounter;\r
1573 Ext.suspendLayouts();\r
1574 },\r
1575\r
1576 onEndUpdate: function() {\r
1577 var me = this;\r
1578\r
1579 if (me.updateSuspendCounter) {\r
1580 --me.updateSuspendCounter;\r
1581 }\r
1582 \r
1583 Ext.resumeLayouts(true);\r
1584 if (me.refreshSizePending) {\r
1585 me.refreshSize(true);\r
1586 me.refreshSizePending = false;\r
1587 }\r
1588 },\r
1589\r
1590 /**\r
1591 * @private\r
1592 * Calls this.refresh if this.blockRefresh is not true\r
1593 * @since 3.4.0\r
1594 */\r
1595 onDataRefresh: function(store) {\r
1596 var me = this,\r
1597 preserveScrollOnRefresh = me.preserveScrollOnRefresh;\r
1598\r
1599 // If this refresh event is fire from a store load, then use the \r
1600 // preserveScrollOnReLoad setting to decide whether to preserve scroll position\r
1601 if (store.loadCount > me.lastRefreshLoadCount) {\r
1602 me.preserveScrollOnRefresh = me.preserveScrollOnReLoad;\r
1603 }\r
1604 me.refreshView();\r
1605 me.preserveScrollOnRefresh = preserveScrollOnRefresh;\r
1606 me.lastRefreshLoadCount = store.loadCount;\r
1607 },\r
1608\r
1609 refreshView: function() {\r
1610 var me = this,\r
1611 // If we have an ancestor in a non-boxready state (collapsed or in-transition, or hidden), then block the\r
1612 // refresh because the next layout will trigger the refresh\r
1613 blocked = me.blockRefresh || !me.rendered || me.up('[collapsed],[isCollapsingOrExpanding],[hidden]');\r
1614\r
1615 // If we are blocked in any way due to either a setting, or hidden or collapsed, or animating ancestor, then\r
1616 // the next refresh attempt at the upcoming layout must not defer.\r
1617 if (blocked) {\r
1618 me.refreshNeeded = true;\r
1619 } else {\r
1620 if (me.bufferedRenderer) {\r
1621 me.bufferedRenderer.refreshView();\r
1622 } else {\r
1623 me.refresh();\r
1624 }\r
1625 }\r
1626 },\r
1627\r
1628 /**\r
1629 * Returns the template node the passed child belongs to, or null if it doesn't belong to one.\r
1630 * @param {HTMLElement} node\r
1631 * @return {HTMLElement} The template node\r
1632 */\r
1633 findItemByChild: function(node){\r
1634 return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());\r
1635 },\r
1636\r
1637 /**\r
1638 * Returns the template node by the Ext.event.Event or null if it is not found.\r
1639 * @param {Ext.event.Event} e\r
1640 */\r
1641 findTargetByEvent: function(e) {\r
1642 return e.getTarget(this.getItemSelector(), this.getTargetEl());\r
1643 },\r
1644\r
1645\r
1646 /**\r
1647 * Gets the currently selected nodes.\r
1648 * @return {HTMLElement[]} An array of HTMLElements\r
1649 * @since 2.3.0\r
1650 */\r
1651 getSelectedNodes: function(){\r
1652 var nodes = [],\r
1653 records = this.getSelectionModel().getSelection(),\r
1654 ln = records.length,\r
1655 i = 0;\r
1656\r
1657 for (; i < ln; i++) {\r
1658 nodes.push(this.getNode(records[i]));\r
1659 }\r
1660\r
1661 return nodes;\r
1662 },\r
1663\r
1664 /**\r
1665 * Gets an array of the records from an array of nodes\r
1666 * @param {HTMLElement[]} nodes The nodes to evaluate\r
1667 * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects\r
1668 * @since 2.3.0\r
1669 */\r
1670 getRecords: function(nodes) {\r
1671 var records = [],\r
1672 i = 0,\r
1673 len = nodes.length,\r
1674 data = this.dataSource.data;\r
1675\r
1676 for (; i < len; i++) {\r
1677 records[records.length] = data.getByKey(nodes[i].getAttribute('data-recordId'));\r
1678 }\r
1679\r
1680 return records;\r
1681 },\r
1682\r
1683 /**\r
1684 * Gets a record from a node\r
1685 * @param {Ext.dom.Element/HTMLElement} node The node to evaluate\r
1686 *\r
1687 * @return {Ext.data.Model} record The {@link Ext.data.Model} object\r
1688 * @since 2.3.0\r
1689 */\r
1690 getRecord: function(node){\r
1691 return this.dataSource.getByInternalId(Ext.getDom(node).getAttribute('data-recordId'));\r
1692 },\r
1693\r
1694\r
1695 /**\r
1696 * Returns true if the passed node is selected, else false.\r
1697 * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check\r
1698 * @return {Boolean} True if selected, else false\r
1699 * @since 2.3.0\r
1700 */\r
1701 isSelected: function(node) {\r
1702 var r = this.getRecord(node);\r
1703 return this.getSelectionModel().isSelected(r);\r
1704 },\r
1705\r
1706 /**\r
1707 * Selects a record instance by record instance or index.\r
1708 * @param {Ext.data.Model[]/Number} records An array of records or an index\r
1709 * @param {Boolean} keepExisting\r
1710 * @param {Boolean} suppressEvent Set to false to not fire a select event\r
1711 * @deprecated 4.0 Use {@link Ext.selection.Model#select} instead.\r
1712 * @since 2.3.0\r
1713 */\r
1714 select: function(records, keepExisting, suppressEvent) {\r
1715 this.getSelectionModel().select(records, keepExisting, suppressEvent);\r
1716 },\r
1717\r
1718 /**\r
1719 * Deselects a record instance by record instance or index.\r
1720 * @param {Ext.data.Model[]/Number} records An array of records or an index\r
1721 * @param {Boolean} suppressEvent Set to false to not fire a deselect event\r
1722 * @since 2.3.0\r
1723 */\r
1724 deselect: function(records, suppressEvent) {\r
1725 this.getSelectionModel().deselect(records, suppressEvent);\r
1726 },\r
1727\r
1728 /**\r
1729 * Gets a template node.\r
1730 * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,\r
1731 * the id of a template node or the record associated with the node.\r
1732 * @return {HTMLElement} The node or null if it wasn't found\r
1733 * @since 2.3.0\r
1734 */\r
1735 getNode: function(nodeInfo) {\r
1736 var me = this,\r
1737 out;\r
1738\r
1739 if (me.rendered && (nodeInfo || nodeInfo === 0)) {\r
1740 if (Ext.isString(nodeInfo)) {\r
1741 // Id\r
1742 out = document.getElementById(nodeInfo);\r
1743 } else if (nodeInfo.isModel) {\r
1744 // Record\r
1745 out = me.getNodeByRecord(nodeInfo);\r
1746 } else if (Ext.isNumber(nodeInfo)) {\r
1747 // Index\r
1748 out = me.all.elements[nodeInfo];\r
1749 } else {\r
1750 if (nodeInfo.target && nodeInfo.target.nodeType) {\r
1751 // An event. Check that target is a node: <a target="_blank"> must pass unchanged\r
1752 nodeInfo = nodeInfo.target;\r
1753 }\r
1754 out = Ext.fly(nodeInfo).findParent(me.itemSelector, me.getTargetEl()); // already an HTMLElement\r
1755 }\r
1756 }\r
1757 return out || null;\r
1758 },\r
1759\r
1760 /**\r
1761 * @private\r
1762 */\r
1763 getNodeByRecord: function(record) {\r
1764 var index = this.store.indexOf(record);\r
1765 return this.all.elements[index] || null;\r
1766 },\r
1767\r
1768 /**\r
1769 * Gets a range nodes.\r
1770 * @param {Number} start (optional) The index of the first node in the range\r
1771 * @param {Number} end (optional) The index of the last node in the range\r
1772 * @return {HTMLElement[]} An array of nodes\r
1773 * @since 2.3.0\r
1774 */\r
1775 getNodes: function(start, end) {\r
1776 var all = this.all;\r
1777\r
1778 if (end !== undefined) {\r
1779 end++;\r
1780 }\r
1781 return all.slice(start, end);\r
1782 },\r
1783\r
1784 /**\r
1785 * Finds the index of the passed node.\r
1786 * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node\r
1787 * or a record associated with a node.\r
1788 * @return {Number} The index of the node or -1\r
1789 * @since 2.3.0\r
1790 */\r
1791 indexOf: function(node) {\r
1792 node = this.getNode(node);\r
1793 if (!node && node !== 0) {\r
1794 return -1;\r
1795 }\r
1796 if (node.getAttribute('data-recordIndex')) {\r
1797 return Number(node.getAttribute('data-recordIndex'));\r
1798 }\r
1799 return this.all.indexOf(node);\r
1800 },\r
1801\r
1802 onDestroy: function() {\r
1803 var me = this,\r
1804 count = me.updateSuspendCounter;\r
1805\r
1806 me.all.clear();\r
1807 me.emptyEl = null;\r
1808 \r
1809 me.callParent();\r
1810 me.bindStore(null);\r
1811 \r
1812 me.store = me.dataSource = me.storeListeners = null;\r
1813 \r
1814 if (me.selModelRelayer) {\r
1815 me.selModelRelayer.destroy();\r
1816 me.selModelRelayer = null;\r
1817 }\r
1818 \r
1819 Ext.destroy(me.navigationModel, me.selectionModel);\r
1820 me.navigationModel = me.selectionModel = me.selModel = null;\r
1821 \r
1822 me.loadMask = null;\r
1823\r
1824 // We have been destroyed during a begin/end update, which means we're\r
1825 // suspending layouts, must forcibly do it here.\r
1826 while (count--) {\r
1827 Ext.resumeLayouts(true);\r
1828 }\r
1829 },\r
1830\r
1831 // invoked by the selection model to maintain visual UI cues\r
1832 onItemSelect: function(record) {\r
1833 var node = this.getNode(record);\r
1834\r
1835 if (node) {\r
1836 Ext.fly(node).addCls(this.selectedItemCls);\r
1837 }\r
1838 },\r
1839\r
1840 // invoked by the selection model to maintain visual UI cues\r
1841 onItemDeselect: function(record) {\r
1842 var node = this.getNode(record);\r
1843\r
1844 if (node) {\r
1845 Ext.fly(node).removeCls(this.selectedItemCls);\r
1846 }\r
1847 },\r
1848\r
1849 getItemSelector: function() {\r
1850 return this.itemSelector;\r
1851 },\r
1852\r
1853 /**\r
1854 * Adds a CSS Class to a specific item.\r
1855 * @param {HTMLElement/String/Number/Ext.data.Model} itemInfo An HTMLElement, index or instance of a model\r
1856 * representing this item\r
1857 * @param {String} cls\r
1858 */\r
1859 addItemCls: function(itemInfo, cls) {\r
1860 var item = this.getNode(itemInfo);\r
1861 if (item) {\r
1862 Ext.fly(item).addCls(cls);\r
1863 }\r
1864 },\r
1865\r
1866 /**\r
1867 * Removes a CSS Class from a specific item.\r
1868 * @param {HTMLElement/String/Number/Ext.data.Model} itemInfo An HTMLElement, index or instance of a model\r
1869 * representing this item\r
1870 * @param {String} cls\r
1871 */\r
1872 removeItemCls: function(itemInfo, cls) {\r
1873 var item = this.getNode(itemInfo);\r
1874 if (item) {\r
1875 Ext.fly(item).removeCls(cls);\r
1876 }\r
1877 },\r
1878\r
1879 setStore: function (newStore) {\r
1880 // Here we want to override the config system setter because setting the store is a special case\r
1881 // that the config system wasn't able to handle.\r
1882 //\r
1883 // For instance, because `bindStore` is the only API for both binding and unbinding a store, we\r
1884 // couldn't unbind the old store using the config system because it would simply unbind the new\r
1885 // store that the setter had just poked onto the instance:\r
1886 //\r
1887 // setStore -> intance.store = newStore\r
1888 // updateStore -> view.unbind(null) (unbinds the newStore)\r
1889 //\r
1890 var me = this;\r
1891\r
1892 if (me.store !== newStore) {\r
1893 if (me.isConfiguring) {\r
1894 me.store = newStore;\r
1895 } else {\r
1896 me.bindStore(newStore, /*initial*/ false);\r
1897 }\r
1898 }\r
1899 },\r
1900\r
1901 privates: {\r
1902 toggleChildrenTabbability: function(enableTabbing) {\r
1903 var focusEl = this.getTargetEl();\r
1904\r
1905 if (enableTabbing) {\r
1906 focusEl.restoreTabbableState(/* skipSelf = */ true);\r
1907 }\r
1908 else {\r
1909 // Do NOT includeSaved\r
1910 // Once an item has had tabbability saved, do not increment its save level\r
1911 focusEl.saveTabbableState({\r
1912 skipSelf: true,\r
1913 includeSaved: false\r
1914 });\r
1915 }\r
1916 },\r
1917\r
1918 /**\r
1919 * @private\r
1920 * Called by refresh to collect the view item nodes.\r
1921 */\r
1922 collectNodes: function(targetEl) {\r
1923 var all = this.all,\r
1924 options = {\r
1925 role: this.itemAriaRole\r
1926 };\r
1927\r
1928 all.fill(Ext.fly(targetEl).query(this.getItemSelector()), all.startIndex || 0);\r
1929\r
1930 // Subclasses may set focusable to false (BoundList is not focusable)\r
1931 if (this.focusable) {\r
1932 options.tabindex = '-1';\r
1933 }\r
1934 \r
1935 all.set(options);\r
1936 },\r
1937\r
1938 createMask: function(mask) {\r
1939 var me = this,\r
1940 maskStore = me.getStore(),\r
1941 cfg;\r
1942\r
1943 if (maskStore && !maskStore.isEmptyStore && !maskStore.loadsSynchronously()) {\r
1944 cfg = {\r
1945 target: me,\r
1946 msg: me.loadingText,\r
1947 useMsg: me.loadingUseMsg,\r
1948 // The store gets bound in initComponent, so while\r
1949 // rendering let's push on the store\r
1950 store: maskStore\r
1951 };\r
1952 // Do not overwrite default msgCls if we do not have a loadingCls\r
1953 if (me.loadingCls) {\r
1954 cfg.msgCls = me.loadingCls;\r
1955 }\r
1956 // either a config object \r
1957 if (Ext.isObject(mask)) {\r
1958 cfg = Ext.apply(cfg, mask);\r
1959 }\r
1960 // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.\r
1961 // If this DataView is floating, then mask this DataView.\r
1962 // Otherwise, mask its owning Container (or this, if there *is* no owning Container).\r
1963 // LoadMask captures the element upon render.\r
1964 me.loadMask = new Ext.LoadMask(cfg);\r
1965 me.loadMask.on({\r
1966 scope: me,\r
1967 beforeshow: me.onMaskBeforeShow,\r
1968 hide: me.onMaskHide\r
1969 });\r
1970 }\r
1971 return me.loadMask;\r
1972 },\r
1973\r
1974 getOverflowEl: function() {\r
1975 // The desired behavior here is just to inherit from the superclass. However,\r
1976 // the superclass method calls this.getTargetEl, which sends us into an infinte\r
1977 // loop because our getTargetEl may call getScrollerEl(), which calls getOverflowEl()\r
1978 return Ext.Component.prototype.getTargetEl.call(this);\r
1979 },\r
1980\r
1981 getTargetEl: function() {\r
1982 return this.touchScroll ? this.getScrollerEl() : this.callParent();\r
1983 }\r
1984 }\r
1985}, function() {\r
1986 // all of this information is available directly\r
1987 // from the SelectionModel itself, the only added methods\r
1988 // to DataView regarding selection will perform some transformation/lookup\r
1989 // between HTMLElement/Nodes to records and vice versa.\r
1990 Ext.deprecate('extjs', '4.0', function() {\r
1991 Ext.view.AbstractView.override({\r
1992 /**\r
1993 * @cfg {Boolean} [multiSelect=false]\r
1994 * True to allow selection of more than one item at a time, false to allow selection of only a single item\r
1995 * at a time or no selection at all, depending on the value of {@link #singleSelect}.\r
1996 * @deprecated 4.0 Use {@link Ext.selection.Model#mode} 'MULTI' instead.\r
1997 * @since 2.3.0\r
1998 */\r
1999 /**\r
2000 * @cfg {Boolean} [singleSelect]\r
2001 * Allows selection of exactly one item at a time. As this is the default selection mode anyway, this config\r
2002 * is completely ignored.\r
2003 * @removed 4.0 Use {@link Ext.selection.Model#mode} 'SINGLE' instead.\r
2004 * @since 2.3.0\r
2005 */\r
2006 /**\r
2007 * @cfg {Boolean} [simpleSelect=false]\r
2008 * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,\r
2009 * false to force the user to hold Ctrl or Shift to select more than on item.\r
2010 * @deprecated 4.0 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.\r
2011 * @since 2.3.0\r
2012 */\r
2013\r
2014 /**\r
2015 * Gets the number of selected nodes.\r
2016 * @return {Number} The node count\r
2017 * @deprecated 4.0 Use {@link Ext.selection.Model#getCount} instead.\r
2018 * @since 2.3.0\r
2019 */\r
2020 getSelectionCount: function(){\r
2021 if (Ext.global.console) {\r
2022 Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");\r
2023 }\r
2024 return this.selModel.getSelection().length;\r
2025 },\r
2026\r
2027 /**\r
2028 * Gets an array of the selected records\r
2029 * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects\r
2030 * @deprecated 4.0 Use {@link Ext.selection.Model#getSelection} instead.\r
2031 * @since 2.3.0\r
2032 */\r
2033 getSelectedRecords: function(){\r
2034 if (Ext.global.console) {\r
2035 Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");\r
2036 }\r
2037 return this.selModel.getSelection();\r
2038 },\r
2039\r
2040 // documented above\r
2041 // @ignore\r
2042 select: function(records, keepExisting, supressEvents) {\r
2043 if (Ext.global.console) {\r
2044 Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");\r
2045 }\r
2046 var sm = this.getSelectionModel();\r
2047 return sm.select.apply(sm, arguments);\r
2048 },\r
2049\r
2050 /**\r
2051 * Deselects all selected records.\r
2052 * @deprecated 4.0 Use {@link Ext.selection.Model#deselectAll} instead.\r
2053 * @since 2.3.0\r
2054 */\r
2055 clearSelections: function() {\r
2056 if (Ext.global.console) {\r
2057 Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");\r
2058 }\r
2059 var sm = this.getSelectionModel();\r
2060 return sm.deselectAll();\r
2061 }\r
2062 });\r
2063 });\r
2064});\r
2065\r