Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Model2.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-selection.Model'>/**
2 </span> * @class Ext.selection.Model
3  * @extends Ext.util.Observable
4  *
5  * Tracks what records are currently selected in a databound widget.
6  *
7  * This is an abstract class and is not meant to be directly used.
8  *
9  * DataBound UI widgets such as GridPanel, TreePanel, and ListView
10  * should subclass AbstractStoreSelectionModel and provide a way
11  * to binding to the component.
12  *
13  * The abstract methods onSelectChange and onLastFocusChanged should
14  * be implemented in these subclasses to update the UI widget.
15  */
16 Ext.define('Ext.selection.Model', {
17     extend: 'Ext.util.Observable',
18     alternateClassName: 'Ext.AbstractStoreSelectionModel',
19     requires: ['Ext.data.StoreManager'],
20     // lastSelected
21
22 <span id='Ext-selection.Model-cfg-mode'>    /**
23 </span>     * @cfg {String} mode
24      * Modes of selection.
25      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'SINGLE'
26      */
27     
28 <span id='Ext-selection.Model-cfg-allowDeselect'>    /**
29 </span>     * @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.
31      */
32     allowDeselect: false,
33
34 <span id='Ext-selection.Model-property-selected'>    /**
35 </span>     * @property selected
36      * READ-ONLY A MixedCollection that maintains all of the currently selected
37      * records.
38      */
39     selected: null,
40     
41     
42 <span id='Ext-selection.Model-property-pruneRemoved'>    /**
43 </span>     * 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.
46      * @private
47      */
48     pruneRemoved: true,
49
50     constructor: function(cfg) {
51         var me = this;
52         
53         cfg = cfg || {};
54         Ext.apply(me, cfg);
55         
56         me.addEvents(
57 <span id='Ext-selection.Model-event-selectionchange'>            /**
58 </span>             * @event selectionchange
59              * Fired after a selection change has occurred
60              * @param {Ext.selection.Model} this
61              * @param  {Array} selected The selected records
62              */
63              'selectionchange'
64         );
65
66         me.modes = {
67             SINGLE: true,
68             SIMPLE: true,
69             MULTI: true
70         };
71
72         // sets this.selectionMode
73         me.setSelectionMode(cfg.mode || me.mode);
74
75         // maintains the currently selected records.
76         me.selected = Ext.create('Ext.util.MixedCollection');
77         
78         me.callParent(arguments);
79     },
80
81     // binds the store to the selModel.
82     bind : function(store, initial){
83         var me = this;
84         
85         if(!initial &amp;&amp; me.store){
86             if(store !== me.store &amp;&amp; me.store.autoDestroy){
87                 me.store.destroy();
88             }else{
89                 me.store.un(&quot;add&quot;, me.onStoreAdd, me);
90                 me.store.un(&quot;clear&quot;, me.onStoreClear, me);
91                 me.store.un(&quot;remove&quot;, me.onStoreRemove, me);
92                 me.store.un(&quot;update&quot;, me.onStoreUpdate, me);
93             }
94         }
95         if(store){
96             store = Ext.data.StoreManager.lookup(store);
97             store.on({
98                 add: me.onStoreAdd,
99                 clear: me.onStoreClear,
100                 remove: me.onStoreRemove,
101                 update: me.onStoreUpdate,
102                 scope: me
103             });
104         }
105         me.store = store;
106         if(store &amp;&amp; !initial) {
107             me.refresh();
108         }
109     },
110
111     selectAll: function(silent) {
112         var selections = this.store.getRange(),
113             i = 0,
114             len = selections.length;
115             
116         for (; i &lt; len; i++) {
117             this.doSelect(selections[i], true, silent);
118         }
119     },
120
121     deselectAll: function() {
122         var selections = this.getSelection(),
123             i = 0,
124             len = selections.length;
125             
126         for (; i &lt; len; i++) {
127             this.doDeselect(selections[i]);
128         }
129     },
130
131     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
132     // selection modes. Requires that an event be passed so that we can know
133     // if user held ctrl or shift.
134     selectWithEvent: function(record, e) {
135         var me = this;
136         
137         switch (me.selectionMode) {
138             case 'MULTI':
139                 if (e.ctrlKey &amp;&amp; me.isSelected(record)) {
140                     me.doDeselect(record, false);
141                 } else if (e.shiftKey &amp;&amp; me.lastFocused) {
142                     me.selectRange(me.lastFocused, record, e.ctrlKey);
143                 } else if (e.ctrlKey) {
144                     me.doSelect(record, true, false);
145                 } else if (me.isSelected(record) &amp;&amp; !e.shiftKey &amp;&amp; !e.ctrlKey &amp;&amp; me.selected.getCount() &gt; 1) {
146                     me.doSelect(record, false, false);
147                 } else {
148                     me.doSelect(record, false);
149                 }
150                 break;
151             case 'SIMPLE':
152                 if (me.isSelected(record)) {
153                     me.doDeselect(record);
154                 } else {
155                     me.doSelect(record, true);
156                 }
157                 break;
158             case 'SINGLE':
159                 // if allowDeselect is on and this record isSelected, deselect it
160                 if (me.allowDeselect &amp;&amp; me.isSelected(record)) {
161                     me.doDeselect(record);
162                 // select the record and do NOT maintain existing selections
163                 } else {
164                     me.doSelect(record, false);
165                 }
166                 break;
167         }
168     },
169
170 <span id='Ext-selection.Model-method-selectRange'>    /**
171 </span>     * Selects a range of rows if the selection model {@link #isLocked is not locked}.
172      * All rows in between startRow and endRow are also selected.
173      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
174      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
175      * @param {Boolean} keepExisting (optional) True to retain existing selections
176      */
177     selectRange : function(startRow, endRow, keepExisting, dir){
178         var me = this,
179             store = me.store,
180             selectedCount = 0,
181             i,
182             tmp,
183             dontDeselect,
184             records = [];
185         
186         if (me.isLocked()){
187             return;
188         }
189         
190         if (!keepExisting) {
191             me.clearSelections();
192         }
193         
194         if (!Ext.isNumber(startRow)) {
195             startRow = store.indexOf(startRow);
196         } 
197         if (!Ext.isNumber(endRow)) {
198             endRow = store.indexOf(endRow);
199         }
200         
201         // swap values
202         if (startRow &gt; endRow){
203             tmp = endRow;
204             endRow = startRow;
205             startRow = tmp;
206         }
207
208         for (i = startRow; i &lt;= endRow; i++) {
209             if (me.isSelected(store.getAt(i))) {
210                 selectedCount++;
211             }
212         }
213
214         if (!dir) {
215             dontDeselect = -1;
216         } else {
217             dontDeselect = (dir == 'up') ? startRow : endRow;
218         }
219         
220         for (i = startRow; i &lt;= endRow; i++){
221             if (selectedCount == (endRow - startRow + 1)) {
222                 if (i != dontDeselect) {
223                     me.doDeselect(i, true);
224                 }
225             } else {
226                 records.push(store.getAt(i));
227             }
228         }
229         me.doMultiSelect(records, true);
230     },
231     
232 <span id='Ext-selection.Model-method-select'>    /**
233 </span>     * Selects a record instance by record instance or index.
234      * @param {Ext.data.Model/Index} records An array of records or an index
235      * @param {Boolean} keepExisting
236      * @param {Boolean} suppressEvent Set to false to not fire a select event
237      */
238     select: function(records, keepExisting, suppressEvent) {
239         this.doSelect(records, keepExisting, suppressEvent);
240     },
241
242 <span id='Ext-selection.Model-method-deselect'>    /**
243 </span>     * Deselects a record instance by record instance or index.
244      * @param {Ext.data.Model/Index} records An array of records or an index
245      * @param {Boolean} suppressEvent Set to false to not fire a deselect event
246      */
247     deselect: function(records, suppressEvent) {
248         this.doDeselect(records, suppressEvent);
249     },
250     
251     doSelect: function(records, keepExisting, suppressEvent) {
252         var me = this,
253             record;
254             
255         if (me.locked) {
256             return;
257         }
258         if (typeof records === &quot;number&quot;) {
259             records = [me.store.getAt(records)];
260         }
261         if (me.selectionMode == &quot;SINGLE&quot; &amp;&amp; records) {
262             record = records.length ? records[0] : records;
263             me.doSingleSelect(record, suppressEvent);
264         } else {
265             me.doMultiSelect(records, keepExisting, suppressEvent);
266         }
267     },
268
269     doMultiSelect: function(records, keepExisting, suppressEvent) {
270         var me = this,
271             selected = me.selected,
272             change = false,
273             i = 0,
274             len, record;
275             
276         if (me.locked) {
277             return;
278         }
279         
280
281         records = !Ext.isArray(records) ? [records] : records;
282         len = records.length;
283         if (!keepExisting &amp;&amp; selected.getCount() &gt; 0) {
284             change = true;
285             me.doDeselect(me.getSelection(), true);
286         }
287
288         for (; i &lt; len; i++) {
289             record = records[i];
290             if (keepExisting &amp;&amp; me.isSelected(record)) {
291                 continue;
292             }
293             change = true;
294             me.lastSelected = record;
295             selected.add(record);
296
297             me.onSelectChange(record, true, suppressEvent);
298         }
299         me.setLastFocused(record, suppressEvent);
300         // fire selchange if there was a change and there is no suppressEvent flag
301         me.maybeFireSelectionChange(change &amp;&amp; !suppressEvent);
302     },
303
304     // records can be an index, a record or an array of records
305     doDeselect: function(records, suppressEvent) {
306         var me = this,
307             selected = me.selected,
308             change = false,
309             i = 0,
310             len, record;
311             
312         if (me.locked) {
313             return;
314         }
315
316         if (typeof records === &quot;number&quot;) {
317             records = [me.store.getAt(records)];
318         }
319
320         records = !Ext.isArray(records) ? [records] : records;
321         len = records.length;
322         for (; i &lt; len; i++) {
323             record = records[i];
324             if (selected.remove(record)) {
325                 if (me.lastSelected == record) {
326                     me.lastSelected = selected.last();
327                 }
328                 me.onSelectChange(record, false, suppressEvent);
329                 change = true;
330             }
331         }
332         // fire selchange if there was a change and there is no suppressEvent flag
333         me.maybeFireSelectionChange(change &amp;&amp; !suppressEvent);
334     },
335
336     doSingleSelect: function(record, suppressEvent) {
337         var me = this,
338             selected = me.selected;
339             
340         if (me.locked) {
341             return;
342         }
343         // already selected.
344         // should we also check beforeselect?
345         if (me.isSelected(record)) {
346             return;
347         }
348         if (selected.getCount() &gt; 0) {
349             me.doDeselect(me.lastSelected, suppressEvent);
350         }
351         selected.add(record);
352         me.lastSelected = record;
353         me.onSelectChange(record, true, suppressEvent);
354         if (!suppressEvent) {
355             me.setLastFocused(record);
356         }
357         me.maybeFireSelectionChange(!suppressEvent);
358     },
359
360 <span id='Ext-selection.Model-method-setLastFocused'>    /**
361 </span>     * @param {Ext.data.Model} record
362      * Set a record as the last focused record. This does NOT mean
363      * that the record has been selected.
364      */
365     setLastFocused: function(record, supressFocus) {
366         var me = this,
367             recordBeforeLast = me.lastFocused;
368         me.lastFocused = record;
369         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
370     },
371     
372 <span id='Ext-selection.Model-method-isFocused'>    /**
373 </span>     * Determines if this record is currently focused.
374      * @param Ext.data.Record record
375      */
376     isFocused: function(record) {
377         return record === this.getLastFocused();
378     },
379
380
381     // fire selection change as long as true is not passed
382     // into maybeFireSelectionChange
383     maybeFireSelectionChange: function(fireEvent) {
384         if (fireEvent) {
385             var me = this;
386             me.fireEvent('selectionchange', me, me.getSelection());
387         }
388     },
389
390 <span id='Ext-selection.Model-method-getLastSelected'>    /**
391 </span>     * Returns the last selected record.
392      */
393     getLastSelected: function() {
394         return this.lastSelected;
395     },
396     
397     getLastFocused: function() {
398         return this.lastFocused;
399     },
400
401 <span id='Ext-selection.Model-method-getSelection'>    /**
402 </span>     * Returns an array of the currently selected records.
403      */
404     getSelection: function() {
405         return this.selected.getRange();
406     },
407
408 <span id='Ext-selection.Model-method-getSelectionMode'>    /**
409 </span>     * Returns the current selectionMode. SINGLE, MULTI or SIMPLE.
410      */
411     getSelectionMode: function() {
412         return this.selectionMode;
413     },
414
415 <span id='Ext-selection.Model-method-setSelectionMode'>    /**
416 </span>     * Sets the current selectionMode. SINGLE, MULTI or SIMPLE.
417      */
418     setSelectionMode: function(selMode) {
419         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
420         // set to mode specified unless it doesnt exist, in that case
421         // use single.
422         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
423     },
424
425 <span id='Ext-selection.Model-method-isLocked'>    /**
426 </span>     * Returns true if the selections are locked.
427      * @return {Boolean}
428      */
429     isLocked: function() {
430         return this.locked;
431     },
432
433 <span id='Ext-selection.Model-method-setLocked'>    /**
434 </span>     * Locks the current selection and disables any changes from
435      * happening to the selection.
436      * @param {Boolean} locked
437      */
438     setLocked: function(locked) {
439         this.locked = !!locked;
440     },
441
442 <span id='Ext-selection.Model-method-isSelected'>    /**
443 </span>     * Returns &lt;tt&gt;true&lt;/tt&gt; if the specified row is selected.
444      * @param {Record/Number} record The record or index of the record to check
445      * @return {Boolean}
446      */
447     isSelected: function(record) {
448         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
449         return this.selected.indexOf(record) !== -1;
450     },
451     
452 <span id='Ext-selection.Model-method-hasSelection'>    /**
453 </span>     * Returns true if there is a selected record.
454      * @return {Boolean}
455      */
456     hasSelection: function() {
457         return this.selected.getCount() &gt; 0;
458     },
459
460     refresh: function() {
461         var me = this,
462             toBeSelected = [],
463             oldSelections = me.getSelection(),
464             len = oldSelections.length,
465             selection,
466             change,
467             i = 0,
468             lastFocused = this.getLastFocused();
469
470         // check to make sure that there are no records
471         // missing after the refresh was triggered, prune
472         // them from what is to be selected if so
473         for (; i &lt; len; i++) {
474             selection = oldSelections[i];
475             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
476                 toBeSelected.push(selection);
477             }
478         }
479
480         // there was a change from the old selected and
481         // the new selection
482         if (me.selected.getCount() != toBeSelected.length) {
483             change = true;
484         }
485
486         me.clearSelections();
487         
488         if (me.store.indexOf(lastFocused) !== -1) {
489             // restore the last focus but supress restoring focus
490             this.setLastFocused(lastFocused, true);
491         }
492
493         if (toBeSelected.length) {
494             // perform the selection again
495             me.doSelect(toBeSelected, false, true);
496         }
497         
498         me.maybeFireSelectionChange(change);
499     },
500
501     clearSelections: function() {
502         // reset the entire selection to nothing
503         var me = this;
504         me.selected.clear();
505         me.lastSelected = null;
506         me.setLastFocused(null);
507     },
508
509     // when a record is added to a store
510     onStoreAdd: function() {
511
512     },
513
514     // when a store is cleared remove all selections
515     // (if there were any)
516     onStoreClear: function() {
517         var me = this,
518             selected = this.selected;
519             
520         if (selected.getCount &gt; 0) {
521             selected.clear();
522             me.lastSelected = null;
523             me.setLastFocused(null);
524             me.maybeFireSelectionChange(true);
525         }
526     },
527
528     // prune records from the SelectionModel if
529     // they were selected at the time they were
530     // removed.
531     onStoreRemove: function(store, record) {
532         var me = this,
533             selected = me.selected;
534             
535         if (me.locked || !me.pruneRemoved) {
536             return;
537         }
538
539         if (selected.remove(record)) {
540             if (me.lastSelected == record) {
541                 me.lastSelected = null;
542             }
543             if (me.getLastFocused() == record) {
544                 me.setLastFocused(null);
545             }
546             me.maybeFireSelectionChange(true);
547         }
548     },
549
550     getCount: function() {
551         return this.selected.getCount();
552     },
553
554     // cleanup.
555     destroy: function() {
556
557     },
558
559     // if records are updated
560     onStoreUpdate: function() {
561
562     },
563
564     // @abstract
565     onSelectChange: function(record, isSelected, suppressEvent) {
566
567     },
568
569     // @abstract
570     onLastFocusChanged: function(oldFocused, newFocused) {
571
572     },
573
574     // @abstract
575     onEditorKey: function(field, e) {
576
577     },
578
579     // @abstract
580     bindComponent: function(cmp) {
581
582     }
583 });</pre></pre></body></html>