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