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