2 * @class Ext.selection.Model
3 * @extends Ext.util.Observable
5 * Tracks what records are currently selected in a databound widget.
7 * This is an abstract class and is not meant to be directly used.
9 * DataBound UI widgets such as GridPanel, TreePanel, and ListView
10 * should subclass AbstractStoreSelectionModel and provide a way
11 * to binding to the component.
13 * The abstract methods onSelectChange and onLastFocusChanged should
14 * be implemented in these subclasses to update the UI widget.
16 Ext.define('Ext.selection.Model', {
17 extend: 'Ext.util.Observable',
18 alternateClassName: 'Ext.AbstractStoreSelectionModel',
19 requires: ['Ext.data.StoreManager'],
25 * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'SINGLE'
29 * @cfg {Boolean} allowDeselect
30 * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the SelectionModel's mode is 'SINGLE'. Defaults to false.
36 * READ-ONLY A MixedCollection that maintains all of the currently selected
43 * Prune records when they are removed from the store from the selection.
44 * This is a private flag. For an example of its usage, take a look at
45 * Ext.selection.TreeModel.
50 constructor: function(cfg) {
58 * @event selectionchange
59 * Fired after a selection change has occurred
60 * @param {Ext.selection.Model} this
61 * @param {Array} selected The selected records
72 // sets this.selectionMode
73 me.setSelectionMode(cfg.mode || me.mode);
75 // maintains the currently selected records.
76 me.selected = Ext.create('Ext.util.MixedCollection');
78 me.callParent(arguments);
81 // binds the store to the selModel.
82 bind : function(store, initial){
85 if(!initial && me.store){
86 if(store !== me.store && me.store.autoDestroy){
89 me.store.un("add", me.onStoreAdd, me);
90 me.store.un("clear", me.onStoreClear, me);
91 me.store.un("remove", me.onStoreRemove, me);
92 me.store.un("update", me.onStoreUpdate, me);
96 store = Ext.data.StoreManager.lookup(store);
99 clear: me.onStoreClear,
100 remove: me.onStoreRemove,
101 update: me.onStoreUpdate,
106 if(store && !initial) {
112 * Select all records in the view.
113 * @param {Boolean} suppressEvent True to suppress any selects event
115 selectAll: function(suppressEvent) {
117 selections = me.store.getRange(),
119 len = selections.length,
120 start = me.getSelection().length;
122 me.bulkChange = true;
123 for (; i < len; i++) {
124 me.doSelect(selections[i], true, suppressEvent);
126 delete me.bulkChange;
127 // fire selection change only if the number of selections differs
128 me.maybeFireSelectionChange(me.getSelection().length !== start);
132 * Deselect all records in the view.
133 * @param {Boolean} suppressEvent True to suppress any deselect events
135 deselectAll: function(suppressEvent) {
137 selections = me.getSelection(),
139 len = selections.length,
140 start = me.getSelection().length;
142 me.bulkChange = true;
143 for (; i < len; i++) {
144 me.doDeselect(selections[i], suppressEvent);
146 delete me.bulkChange;
147 // fire selection change only if the number of selections differs
148 me.maybeFireSelectionChange(me.getSelection().length !== start);
151 // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
152 // selection modes. Requires that an event be passed so that we can know
153 // if user held ctrl or shift.
154 selectWithEvent: function(record, e) {
157 switch (me.selectionMode) {
159 if (e.ctrlKey && me.isSelected(record)) {
160 me.doDeselect(record, false);
161 } else if (e.shiftKey && me.lastFocused) {
162 me.selectRange(me.lastFocused, record, e.ctrlKey);
163 } else if (e.ctrlKey) {
164 me.doSelect(record, true, false);
165 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
166 me.doSelect(record, false, false);
168 me.doSelect(record, false);
172 if (me.isSelected(record)) {
173 me.doDeselect(record);
175 me.doSelect(record, true);
179 // if allowDeselect is on and this record isSelected, deselect it
180 if (me.allowDeselect && me.isSelected(record)) {
181 me.doDeselect(record);
182 // select the record and do NOT maintain existing selections
184 me.doSelect(record, false);
191 * Selects a range of rows if the selection model {@link #isLocked is not locked}.
192 * All rows in between startRow and endRow are also selected.
193 * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
194 * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
195 * @param {Boolean} keepExisting (optional) True to retain existing selections
197 selectRange : function(startRow, endRow, keepExisting, dir){
211 me.clearSelections();
214 if (!Ext.isNumber(startRow)) {
215 startRow = store.indexOf(startRow);
217 if (!Ext.isNumber(endRow)) {
218 endRow = store.indexOf(endRow);
222 if (startRow > endRow){
228 for (i = startRow; i <= endRow; i++) {
229 if (me.isSelected(store.getAt(i))) {
237 dontDeselect = (dir == 'up') ? startRow : endRow;
240 for (i = startRow; i <= endRow; i++){
241 if (selectedCount == (endRow - startRow + 1)) {
242 if (i != dontDeselect) {
243 me.doDeselect(i, true);
246 records.push(store.getAt(i));
249 me.doMultiSelect(records, true);
253 * Selects a record instance by record instance or index.
254 * @param {Ext.data.Model/Index} records An array of records or an index
255 * @param {Boolean} keepExisting
256 * @param {Boolean} suppressEvent Set to false to not fire a select event
258 select: function(records, keepExisting, suppressEvent) {
259 this.doSelect(records, keepExisting, suppressEvent);
263 * Deselects a record instance by record instance or index.
264 * @param {Ext.data.Model/Index} records An array of records or an index
265 * @param {Boolean} suppressEvent Set to false to not fire a deselect event
267 deselect: function(records, suppressEvent) {
268 this.doDeselect(records, suppressEvent);
271 doSelect: function(records, keepExisting, suppressEvent) {
278 if (typeof records === "number") {
279 records = [me.store.getAt(records)];
281 if (me.selectionMode == "SINGLE" && records) {
282 record = records.length ? records[0] : records;
283 me.doSingleSelect(record, suppressEvent);
285 me.doMultiSelect(records, keepExisting, suppressEvent);
289 doMultiSelect: function(records, keepExisting, suppressEvent) {
291 selected = me.selected,
301 records = !Ext.isArray(records) ? [records] : records;
302 len = records.length;
303 if (!keepExisting && selected.getCount() > 0) {
305 me.doDeselect(me.getSelection(), suppressEvent);
308 for (; i < len; i++) {
310 if (keepExisting && me.isSelected(record)) {
314 me.lastSelected = record;
315 selected.add(record);
317 me.onSelectChange(record, true, suppressEvent);
319 me.setLastFocused(record, suppressEvent);
320 // fire selchange if there was a change and there is no suppressEvent flag
321 me.maybeFireSelectionChange(change && !suppressEvent);
324 // records can be an index, a record or an array of records
325 doDeselect: function(records, suppressEvent) {
327 selected = me.selected,
336 if (typeof records === "number") {
337 records = [me.store.getAt(records)];
340 records = !Ext.isArray(records) ? [records] : records;
341 len = records.length;
342 for (; i < len; i++) {
344 if (selected.remove(record)) {
345 if (me.lastSelected == record) {
346 me.lastSelected = selected.last();
348 me.onSelectChange(record, false, suppressEvent);
352 // fire selchange if there was a change and there is no suppressEvent flag
353 me.maybeFireSelectionChange(change && !suppressEvent);
356 doSingleSelect: function(record, suppressEvent) {
358 selected = me.selected;
364 // should we also check beforeselect?
365 if (me.isSelected(record)) {
368 if (selected.getCount() > 0) {
369 me.doDeselect(me.lastSelected, suppressEvent);
371 selected.add(record);
372 me.lastSelected = record;
373 me.onSelectChange(record, true, suppressEvent);
374 if (!suppressEvent) {
375 me.setLastFocused(record);
377 me.maybeFireSelectionChange(!suppressEvent);
381 * @param {Ext.data.Model} record
382 * Set a record as the last focused record. This does NOT mean
383 * that the record has been selected.
385 setLastFocused: function(record, supressFocus) {
387 recordBeforeLast = me.lastFocused;
388 me.lastFocused = record;
389 me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
393 * Determines if this record is currently focused.
394 * @param Ext.data.Record record
396 isFocused: function(record) {
397 return record === this.getLastFocused();
401 // fire selection change as long as true is not passed
402 // into maybeFireSelectionChange
403 maybeFireSelectionChange: function(fireEvent) {
405 if (fireEvent && !me.bulkChange) {
406 me.fireEvent('selectionchange', me, me.getSelection());
411 * Returns the last selected record.
413 getLastSelected: function() {
414 return this.lastSelected;
417 getLastFocused: function() {
418 return this.lastFocused;
422 * Returns an array of the currently selected records.
424 getSelection: function() {
425 return this.selected.getRange();
429 * Returns the current selectionMode. SINGLE, MULTI or SIMPLE.
431 getSelectionMode: function() {
432 return this.selectionMode;
436 * Sets the current selectionMode. SINGLE, MULTI or SIMPLE.
438 setSelectionMode: function(selMode) {
439 selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
440 // set to mode specified unless it doesnt exist, in that case
442 this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
446 * Returns true if the selections are locked.
449 isLocked: function() {
454 * Locks the current selection and disables any changes from
455 * happening to the selection.
456 * @param {Boolean} locked
458 setLocked: function(locked) {
459 this.locked = !!locked;
463 * Returns <tt>true</tt> if the specified row is selected.
464 * @param {Record/Number} record The record or index of the record to check
467 isSelected: function(record) {
468 record = Ext.isNumber(record) ? this.store.getAt(record) : record;
469 return this.selected.indexOf(record) !== -1;
473 * Returns true if there is a selected record.
476 hasSelection: function() {
477 return this.selected.getCount() > 0;
480 refresh: function() {
483 oldSelections = me.getSelection(),
484 len = oldSelections.length,
488 lastFocused = this.getLastFocused();
490 // check to make sure that there are no records
491 // missing after the refresh was triggered, prune
492 // them from what is to be selected if so
493 for (; i < len; i++) {
494 selection = oldSelections[i];
495 if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
496 toBeSelected.push(selection);
500 // there was a change from the old selected and
502 if (me.selected.getCount() != toBeSelected.length) {
506 me.clearSelections();
508 if (me.store.indexOf(lastFocused) !== -1) {
509 // restore the last focus but supress restoring focus
510 this.setLastFocused(lastFocused, true);
513 if (toBeSelected.length) {
514 // perform the selection again
515 me.doSelect(toBeSelected, false, true);
518 me.maybeFireSelectionChange(change);
522 * A fast reset of the selections without firing events, updating the ui, etc.
523 * For private usage only.
526 clearSelections: function() {
527 // reset the entire selection to nothing
530 me.lastSelected = null;
531 me.setLastFocused(null);
534 // when a record is added to a store
535 onStoreAdd: function() {
539 // when a store is cleared remove all selections
540 // (if there were any)
541 onStoreClear: function() {
543 selected = this.selected;
545 if (selected.getCount > 0) {
547 me.lastSelected = null;
548 me.setLastFocused(null);
549 me.maybeFireSelectionChange(true);
553 // prune records from the SelectionModel if
554 // they were selected at the time they were
556 onStoreRemove: function(store, record) {
558 selected = me.selected;
560 if (me.locked || !me.pruneRemoved) {
564 if (selected.remove(record)) {
565 if (me.lastSelected == record) {
566 me.lastSelected = null;
568 if (me.getLastFocused() == record) {
569 me.setLastFocused(null);
571 me.maybeFireSelectionChange(true);
575 getCount: function() {
576 return this.selected.getCount();
580 destroy: function() {
584 // if records are updated
585 onStoreUpdate: function() {
590 onSelectChange: function(record, isSelected, suppressEvent) {
595 onLastFocusChanged: function(oldFocused, newFocused) {
600 onEditorKey: function(field, e) {
605 bindComponent: function(cmp) {