]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/mixin/Selectable.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / mixin / Selectable.js
CommitLineData
6527f429
DM
1/**\r
2 * Tracks what records are currently selected in a databound widget. This class is mixed in to\r
3 * {@link Ext.view.View dataview} and all subclasses.\r
4 * @private\r
5 */\r
6Ext.define('Ext.mixin.Selectable', {\r
7 extend: 'Ext.Mixin',\r
8\r
9 mixinConfig: {\r
10 id: 'selectable',\r
11 after: {\r
12 updateStore: 'updateStore'\r
13 }\r
14 },\r
15\r
16 /**\r
17 * @event beforeselectionchange\r
18 * Fires before an item is selected.\r
19 * @param {Ext.mixin.Selectable} this\r
20 * @preventable\r
21 * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.\r
22 */\r
23\r
24 /**\r
25 * @event selectionchange\r
26 * Fires when a selection changes.\r
27 * @param {Ext.mixin.Selectable} this\r
28 * @param {Ext.data.Model[]} records The records whose selection has changed.\r
29 */\r
30\r
31 config: {\r
32 /**\r
33 * @cfg {Boolean} disableSelection `true` to disable selection.\r
34 * This configuration will lock the selection model that the DataView uses.\r
35 * @accessor\r
36 */\r
37 disableSelection: null,\r
38\r
39 /**\r
40 * @cfg {String} mode\r
41 * Modes of selection.\r
42 * Valid values are `'SINGLE'`, `'SIMPLE'`, and `'MULTI'`.\r
43 * @accessor\r
44 */\r
45 mode: 'SINGLE',\r
46\r
47 /**\r
48 * @cfg {Boolean} allowDeselect\r
49 * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the Selectable's `mode` is\r
50 * `'SINGLE'`.\r
51 * @accessor\r
52 */\r
53 allowDeselect: false,\r
54\r
55 /**\r
56 * @cfg {Ext.data.Model} lastSelected\r
57 * @private\r
58 * @accessor\r
59 */\r
60 lastSelected: null,\r
61\r
62 /**\r
63 * @cfg {Ext.data.Model} lastFocused\r
64 * @private\r
65 * @accessor\r
66 */\r
67 lastFocused: null,\r
68\r
69 /**\r
70 * @cfg {Boolean} deselectOnContainerClick `true` to deselect current selection when the container body is\r
71 * clicked.\r
72 * @accessor\r
73 */\r
74 deselectOnContainerClick: true,\r
75\r
76 /**\r
77 * @cfg {Ext.data.Model} selection\r
78 * The selected record.\r
79 */\r
80 selection: null,\r
81\r
82 twoWayBindable: {\r
83 selection: 1\r
84 },\r
85\r
86 publishes: {\r
87 selection: 1\r
88 }\r
89 },\r
90\r
91 modes: {\r
92 SINGLE: true,\r
93 SIMPLE: true,\r
94 MULTI: true\r
95 },\r
96\r
97 selectableEventHooks: {\r
98 add: 'onSelectionStoreAdd',\r
99 remove: 'onSelectionStoreRemove',\r
100 update: 'onSelectionStoreUpdate',\r
101 clear: {\r
102 fn: 'onSelectionStoreClear',\r
103 priority: 1000\r
104 },\r
105 load: 'refreshSelection',\r
106 refresh: 'refreshSelection'\r
107 },\r
108\r
109 constructor: function() {\r
110 this.selected = new Ext.util.MixedCollection();\r
111 this.callParent(arguments);\r
112 },\r
113\r
114 initSelectable: function() {\r
115 this.publishState('selection', this.getSelection());\r
116 },\r
117\r
118 /**\r
119 * @private\r
120 */\r
121 applyMode: function(mode) {\r
122 mode = mode ? mode.toUpperCase() : 'SINGLE';\r
123 // set to mode specified unless it doesnt exist, in that case\r
124 // use single.\r
125 return this.modes[mode] ? mode : 'SINGLE';\r
126 },\r
127\r
128 /**\r
129 * @private\r
130 */\r
131 updateStore: function(newStore, oldStore) {\r
132 var me = this,\r
133 bindEvents = Ext.apply({}, me.selectableEventHooks, { scope: me });\r
134\r
135 if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {\r
136 if (oldStore.autoDestroy) {\r
137 oldStore.destroy();\r
138 } else {\r
139 oldStore.un(bindEvents);\r
140 }\r
141 }\r
142\r
143 if (newStore) {\r
144 newStore.on(bindEvents);\r
145 me.refreshSelection();\r
146 }\r
147 },\r
148\r
149 /**\r
150 * Selects all records.\r
151 * @param {Boolean} silent `true` to suppress all select events.\r
152 */\r
153 selectAll: function(silent) {\r
154 var me = this,\r
155 selections = me.getStore().getRange();\r
156\r
157 me.select(selections, true, silent);\r
158 },\r
159\r
160 /**\r
161 * Deselects all records.\r
162 */\r
163 deselectAll: function(supress) {\r
164 var me = this,\r
165 selections = me.getStore().getRange();\r
166\r
167 me.deselect(selections, supress);\r
168\r
169 me.selected.clear();\r
170 me.setLastSelected(null);\r
171 me.setLastFocused(null);\r
172 },\r
173\r
174 updateSelection: function(selection) {\r
175 if (this.changingSelection) {\r
176 return;\r
177 }\r
178\r
179 if (selection) {\r
180 this.select(selection);\r
181 } else {\r
182 this.deselectAll();\r
183 }\r
184 },\r
185\r
186 // Provides differentiation of logic between MULTI, SIMPLE and SINGLE\r
187 // selection modes.\r
188 selectWithEvent: function(record) {\r
189 var me = this,\r
190 isSelected = me.isSelected(record);\r
191 switch (me.getMode()) {\r
192 case 'MULTI':\r
193 case 'SIMPLE':\r
194 if (isSelected) {\r
195 me.deselect(record);\r
196 }\r
197 else {\r
198 me.select(record, true);\r
199 }\r
200 break;\r
201 case 'SINGLE':\r
202 if (me.getAllowDeselect() && isSelected) {\r
203 // if allowDeselect is on and this record isSelected, deselect it\r
204 me.deselect(record);\r
205 } else {\r
206 // select the record and do NOT maintain existing selections\r
207 me.select(record, false);\r
208 }\r
209 break;\r
210 }\r
211 },\r
212\r
213 /**\r
214 * Selects a range of rows if the selection model {@link Ext.mixin.Selectable#getDisableSelection} is not locked.\r
215 * All rows in between `startRecord` and `endRecord` are also selected.\r
216 * @param {Number} startRecord The index of the first row in the range.\r
217 * @param {Number} endRecord The index of the last row in the range.\r
218 * @param {Boolean} [keepExisting] `true` to retain existing selections.\r
219 */\r
220 selectRange: function(startRecord, endRecord, keepExisting) {\r
221 var me = this,\r
222 store = me.getStore(),\r
223 records = [],\r
224 tmp, i;\r
225\r
226 if (me.getDisableSelection()) {\r
227 return;\r
228 }\r
229\r
230 // swap values\r
231 if (startRecord > endRecord) {\r
232 tmp = endRecord;\r
233 endRecord = startRecord;\r
234 startRecord = tmp;\r
235 }\r
236\r
237 for (i = startRecord; i <= endRecord; i++) {\r
238 records.push(store.getAt(i));\r
239 }\r
240 this.doMultiSelect(records, keepExisting);\r
241 },\r
242\r
243 /**\r
244 * Adds the given records to the currently selected set.\r
245 * @param {Ext.data.Model/Array/Number} records The records to select.\r
246 * @param {Boolean} keepExisting If `true`, the existing selection will be added to (if not, the old selection is replaced).\r
247 * @param {Boolean} suppressEvent If `true`, the `select` event will not be fired.\r
248 */\r
249 select: function(records, keepExisting, suppressEvent) {\r
250 var me = this,\r
251 record;\r
252\r
253 if (me.getDisableSelection()) {\r
254 return;\r
255 }\r
256\r
257 if (typeof records === "number") {\r
258 records = [me.getStore().getAt(records)];\r
259 }\r
260\r
261 if (!records) {\r
262 return;\r
263 }\r
264\r
265 if (me.getMode() == "SINGLE" && records) {\r
266 record = records.length ? records[0] : records;\r
267 me.doSingleSelect(record, suppressEvent);\r
268 } else {\r
269 me.doMultiSelect(records, keepExisting, suppressEvent);\r
270 }\r
271 },\r
272\r
273 /**\r
274 * Selects a single record.\r
275 * @private\r
276 */\r
277 doSingleSelect: function(record, suppressEvent) {\r
278 var me = this,\r
279 selected = me.selected;\r
280\r
281 if (me.getDisableSelection()) {\r
282 return;\r
283 }\r
284\r
285 // already selected.\r
286 // should we also check beforeselect?\r
287 if (me.isSelected(record)) {\r
288 return;\r
289 }\r
290\r
291 if (selected.getCount() > 0) {\r
292 me.deselect(me.getLastSelected(), suppressEvent);\r
293 }\r
294\r
295 selected.add(record);\r
296 me.setLastSelected(record);\r
297 me.onItemSelect(record, suppressEvent);\r
298 me.setLastFocused(record);\r
299\r
300 if (!suppressEvent) {\r
301 me.fireSelectionChange([record]);\r
302 }\r
303 },\r
304\r
305 /**\r
306 * Selects a set of multiple records.\r
307 * @private\r
308 */\r
309 doMultiSelect: function(records, keepExisting, suppressEvent) {\r
310 if (records === null || this.getDisableSelection()) {\r
311 return;\r
312 }\r
313 records = !Ext.isArray(records) ? [records] : records;\r
314\r
315 var me = this,\r
316 selected = me.selected,\r
317 ln = records.length,\r
318 change = false,\r
319 i = 0,\r
320 record;\r
321\r
322 if (!keepExisting && selected.getCount() > 0) {\r
323 change = true;\r
324 me.deselect(me.getSelections(), true);\r
325 }\r
326 for (; i < ln; i++) {\r
327 record = records[i];\r
328 if (keepExisting && me.isSelected(record)) {\r
329 continue;\r
330 }\r
331 change = true;\r
332 me.setLastSelected(record);\r
333 selected.add(record);\r
334 if (!suppressEvent) {\r
335 me.setLastFocused(record);\r
336 }\r
337\r
338 me.onItemSelect(record, suppressEvent);\r
339 }\r
340 if (change && !suppressEvent) {\r
341 this.fireSelectionChange(records);\r
342 }\r
343 },\r
344\r
345 /**\r
346 * Deselects the given record(s). If many records are currently selected, it will only deselect those you pass in.\r
347 * @param {Number/Array/Ext.data.Model} records The record(s) to deselect. Can also be a number to reference by index.\r
348 * @param {Boolean} suppressEvent If `true` the `deselect` event will not be fired.\r
349 */\r
350 deselect: function(records, suppressEvent) {\r
351 var me = this;\r
352\r
353 if (me.getDisableSelection()) {\r
354 return;\r
355 }\r
356\r
357 records = Ext.isArray(records) ? records : [records];\r
358\r
359 var selected = me.selected,\r
360 change = false,\r
361 i = 0,\r
362 store = me.getStore(),\r
363 ln = records.length,\r
364 record;\r
365\r
366 for (; i < ln; i++) {\r
367 record = records[i];\r
368\r
369 if (typeof record === 'number') {\r
370 record = store.getAt(record);\r
371 }\r
372\r
373 if (selected.remove(record)) {\r
374 if (me.getLastSelected() == record) {\r
375 me.setLastSelected(selected.last());\r
376 }\r
377 change = true;\r
378 }\r
379 if (record) {\r
380 me.onItemDeselect(record, suppressEvent);\r
381 }\r
382 }\r
383\r
384 if (change && !suppressEvent) {\r
385 me.fireSelectionChange(records);\r
386 }\r
387 },\r
388\r
389 /**\r
390 * Sets a record as the last focused record. This does NOT mean\r
391 * that the record has been selected.\r
392 * @param {Ext.data.Record} newRecord\r
393 * @param {Ext.data.Record} oldRecord\r
394 */\r
395 updateLastFocused: function(newRecord, oldRecord) {\r
396 this.onLastFocusChanged(oldRecord, newRecord);\r
397 },\r
398\r
399 fireSelectionChange: function(records) {\r
400 var me = this;\r
401\r
402 me.changingSelection = true;\r
403 me.setSelection(me.getLastSelected() || null);\r
404 me.changingSelection = false;\r
405 me.fireAction('selectionchange', [me, records], 'getSelections');\r
406 },\r
407\r
408 /**\r
409 * Returns the currently selected records.\r
410 * @return {Ext.data.Model[]} The selected records.\r
411 */\r
412 getSelections: function() {\r
413 return this.selected.getRange();\r
414 },\r
415\r
416 /**\r
417 * Returns `true` if the specified row is selected.\r
418 * @param {Ext.data.Model/Number} record The record or index of the record to check.\r
419 * @return {Boolean}\r
420 */\r
421 isSelected: function(record) {\r
422 record = Ext.isNumber(record) ? this.getStore().getAt(record) : record;\r
423 return this.selected.indexOf(record) !== -1;\r
424 },\r
425\r
426 /**\r
427 * Returns `true` if there is a selected record.\r
428 * @return {Boolean}\r
429 */\r
430 hasSelection: function() {\r
431 return this.selected.getCount() > 0;\r
432 },\r
433\r
434 /**\r
435 * @private\r
436 */\r
437 refreshSelection: function() {\r
438 var me = this,\r
439 selections = me.getSelections();\r
440\r
441 me.deselectAll(true);\r
442 if (selections.length) {\r
443 me.select(selections, false, true);\r
444 }\r
445 },\r
446\r
447 // prune records from the SelectionModel if\r
448 // they were selected at the time they were\r
449 // removed.\r
450 onSelectionStoreRemove: function(store, records) {\r
451 var me = this,\r
452 selected = me.selected,\r
453 ln = records.length,\r
454 removed, record, i;\r
455\r
456 if (me.getDisableSelection()) {\r
457 return;\r
458 }\r
459\r
460 for (i = 0; i < ln; i++) {\r
461 record = records[i];\r
462 if (selected.remove(record)) {\r
463 if (me.getLastSelected() == record) {\r
464 me.setLastSelected(null);\r
465 }\r
466 if (me.getLastFocused() == record) {\r
467 me.setLastFocused(null);\r
468 }\r
469 removed = removed || [];\r
470 removed.push(record);\r
471 }\r
472 }\r
473\r
474 if (removed) {\r
475 me.fireSelectionChange([removed]);\r
476 }\r
477 },\r
478\r
479 onSelectionStoreClear: function(store) {\r
480 var records = store.getData().items;\r
481 this.onSelectionStoreRemove(store, records);\r
482 },\r
483\r
484 /**\r
485 * Returns the number of selections.\r
486 * @return {Number}\r
487 */\r
488 getSelectionCount: function() {\r
489 return this.selected.getCount();\r
490 },\r
491\r
492 onSelectionStoreAdd: Ext.emptyFn,\r
493 onSelectionStoreUpdate: Ext.emptyFn,\r
494 onItemSelect: Ext.emptyFn,\r
495 onItemDeselect: Ext.emptyFn,\r
496 onLastFocusChanged: Ext.emptyFn,\r
497 onEditorKey: Ext.emptyFn\r
498}, function() {\r
499 /**\r
500 * Selects a record instance by record instance or index.\r
501 * @member Ext.mixin.Selectable\r
502 * @method doSelect\r
503 * @param {Ext.data.Model/Number} records An array of records or an index.\r
504 * @param {Boolean} keepExisting\r
505 * @param {Boolean} suppressEvent Set to `false` to not fire a select event.\r
506 * @deprecated 2.0.0 Please use {@link #select} instead.\r
507 */\r
508\r
509 /**\r
510 * Deselects a record instance by record instance or index.\r
511 * @member Ext.mixin.Selectable\r
512 * @method doDeselect\r
513 * @param {Ext.data.Model/Number} records An array of records or an index.\r
514 * @param {Boolean} suppressEvent Set to `false` to not fire a deselect event.\r
515 * @deprecated 2.0.0 Please use {@link #deselect} instead.\r
516 */\r
517\r
518 /**\r
519 * Returns the selection mode currently used by this Selectable.\r
520 * @member Ext.mixin.Selectable\r
521 * @method getSelectionMode\r
522 * @return {String} The current mode.\r
523 * @deprecated 2.0.0 Please use {@link #getMode} instead.\r
524 */\r
525\r
526 /**\r
527 * Returns the array of previously selected items.\r
528 * @member Ext.mixin.Selectable\r
529 * @method getLastSelected\r
530 * @return {Array} The previous selection.\r
531 * @deprecated 2.0.0\r
532 */\r
533\r
534 /**\r
535 * Returns `true` if the Selectable is currently locked.\r
536 * @member Ext.mixin.Selectable\r
537 * @method isLocked\r
538 * @return {Boolean} True if currently locked\r
539 * @deprecated 2.0.0 Please use {@link #getDisableSelection} instead.\r
540 */\r
541\r
542 /**\r
543 * This was an internal function accidentally exposed in 1.x and now deprecated. Calling it has no effect\r
544 * @member Ext.mixin.Selectable\r
545 * @method setLastFocused\r
546 * @deprecated 2.0.0\r
547 */\r
548\r
549 /**\r
550 * Deselects any currently selected records and clears all stored selections.\r
551 * @member Ext.mixin.Selectable\r
552 * @method clearSelections\r
553 * @deprecated 2.0.0 Please use {@link #deselectAll} instead.\r
554 */\r
555\r
556 /**\r
557 * Returns the number of selections.\r
558 * @member Ext.mixin.Selectable\r
559 * @method getCount\r
560 * @return {Number}\r
561 * @deprecated 2.0.0 Please use {@link #getSelectionCount} instead.\r
562 */\r
563\r
564 /**\r
565 * @cfg {Boolean} locked\r
566 * @inheritdoc Ext.mixin.Selectable#disableSelection\r
567 * @deprecated 2.0.0 Please use {@link #disableSelection} instead.\r
568 */\r
569});\r