]>
Commit | Line | Data |
---|---|---|
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 | |
6 | Ext.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 |