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