Upgrade to ExtJS 4.0.2 - Released 06/09/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.AbstractSelectionModel',
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, keepExisting) {
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, keepExisting, 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.deselectAll(true);
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             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
322                 return;
323             }
324             // TODO - coalesce the selectionchange event in deselect w/the one below...
325         }
326
327         function commit () {
328             selected.add(record);
329             change = true;
330         }
331
332         for (; i &lt; len; i++) {
333             record = records[i];
334             if (keepExisting &amp;&amp; me.isSelected(record)) {
335                 continue;
336             }
337             me.lastSelected = record;
338
339             me.onSelectChange(record, true, suppressEvent, commit);
340         }
341         me.setLastFocused(record, suppressEvent);
342         // fire selchange if there was a change and there is no suppressEvent flag
343         me.maybeFireSelectionChange(change &amp;&amp; !suppressEvent);
344     },
345
346     // records can be an index, a record or an array of records
347     doDeselect: function(records, suppressEvent) {
348         var me = this,
349             selected = me.selected,
350             i = 0,
351             len, record,
352             attempted = 0,
353             accepted = 0;
354
355         if (me.locked) {
356             return false;
357         }
358
359         if (typeof records === &quot;number&quot;) {
360             records = [me.store.getAt(records)];
361         } else if (!Ext.isArray(records)) {
362             records = [records];
363         }
364
365         function commit () {
366             ++accepted;
367             selected.remove(record);
368         }
369
370         len = records.length;
371         
372         for (; i &lt; len; i++) {
373             record = records[i];
374             if (me.isSelected(record)) {
375                 if (me.lastSelected == record) {
376                     me.lastSelected = selected.last();
377                 }
378                 ++attempted;
379                 me.onSelectChange(record, false, suppressEvent, commit);
380             }
381         }
382
383         // fire selchange if there was a change and there is no suppressEvent flag
384         me.maybeFireSelectionChange(accepted &gt; 0 &amp;&amp; !suppressEvent);
385         return accepted === attempted;
386     },
387
388     doSingleSelect: function(record, suppressEvent) {
389         var me = this,
390             changed = false,
391             selected = me.selected;
392
393         if (me.locked) {
394             return;
395         }
396         // already selected.
397         // should we also check beforeselect?
398         if (me.isSelected(record)) {
399             return;
400         }
401
402         function commit () {
403             me.bulkChange = true;
404             if (selected.getCount() &gt; 0 &amp;&amp; me.doDeselect(me.lastSelected, suppressEvent) === false) {
405                 delete me.bulkChange;
406                 return false;
407             }
408             delete me.bulkChange;
409
410             selected.add(record);
411             me.lastSelected = record;
412             changed = true;
413         }
414
415         me.onSelectChange(record, true, suppressEvent, commit);
416
417         if (changed) {
418             if (!suppressEvent) {
419                 me.setLastFocused(record);
420             }
421             me.maybeFireSelectionChange(!suppressEvent);
422         }
423     },
424
425 <span id='Ext-selection-Model-method-setLastFocused'>    /**
426 </span>     * @param {Ext.data.Model} record
427      * Set a record as the last focused record. This does NOT mean
428      * that the record has been selected.
429      */
430     setLastFocused: function(record, supressFocus) {
431         var me = this,
432             recordBeforeLast = me.lastFocused;
433         me.lastFocused = record;
434         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
435     },
436
437 <span id='Ext-selection-Model-method-isFocused'>    /**
438 </span>     * Determines if this record is currently focused.
439      * @param Ext.data.Record record
440      */
441     isFocused: function(record) {
442         return record === this.getLastFocused();
443     },
444
445
446     // fire selection change as long as true is not passed
447     // into maybeFireSelectionChange
448     maybeFireSelectionChange: function(fireEvent) {
449         var me = this;
450         if (fireEvent &amp;&amp; !me.bulkChange) {
451             me.fireEvent('selectionchange', me, me.getSelection());
452         }
453     },
454
455 <span id='Ext-selection-Model-method-getLastSelected'>    /**
456 </span>     * Returns the last selected record.
457      */
458     getLastSelected: function() {
459         return this.lastSelected;
460     },
461
462     getLastFocused: function() {
463         return this.lastFocused;
464     },
465
466 <span id='Ext-selection-Model-method-getSelection'>    /**
467 </span>     * Returns an array of the currently selected records.
468      * @return {Array} The selected records
469      */
470     getSelection: function() {
471         return this.selected.getRange();
472     },
473
474 <span id='Ext-selection-Model-method-getSelectionMode'>    /**
475 </span>     * Returns the current selectionMode. SINGLE, MULTI or SIMPLE.
476      * @return {String} The selectionMode
477      */
478     getSelectionMode: function() {
479         return this.selectionMode;
480     },
481
482 <span id='Ext-selection-Model-method-setSelectionMode'>    /**
483 </span>     * Sets the current selectionMode. SINGLE, MULTI or SIMPLE.
484      */
485     setSelectionMode: function(selMode) {
486         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
487         // set to mode specified unless it doesnt exist, in that case
488         // use single.
489         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
490     },
491
492 <span id='Ext-selection-Model-method-isLocked'>    /**
493 </span>     * Returns true if the selections are locked.
494      * @return {Boolean}
495      */
496     isLocked: function() {
497         return this.locked;
498     },
499
500 <span id='Ext-selection-Model-method-setLocked'>    /**
501 </span>     * Locks the current selection and disables any changes from
502      * happening to the selection.
503      * @param {Boolean} locked
504      */
505     setLocked: function(locked) {
506         this.locked = !!locked;
507     },
508
509 <span id='Ext-selection-Model-method-isSelected'>    /**
510 </span>     * Returns &lt;tt&gt;true&lt;/tt&gt; if the specified row is selected.
511      * @param {Record/Number} record The record or index of the record to check
512      * @return {Boolean}
513      */
514     isSelected: function(record) {
515         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
516         return this.selected.indexOf(record) !== -1;
517     },
518
519 <span id='Ext-selection-Model-method-hasSelection'>    /**
520 </span>     * Returns true if there are any a selected records.
521      * @return {Boolean}
522      */
523     hasSelection: function() {
524         return this.selected.getCount() &gt; 0;
525     },
526
527     refresh: function() {
528         var me = this,
529             toBeSelected = [],
530             oldSelections = me.getSelection(),
531             len = oldSelections.length,
532             selection,
533             change,
534             i = 0,
535             lastFocused = this.getLastFocused();
536
537         // check to make sure that there are no records
538         // missing after the refresh was triggered, prune
539         // them from what is to be selected if so
540         for (; i &lt; len; i++) {
541             selection = oldSelections[i];
542             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
543                 toBeSelected.push(selection);
544             }
545         }
546
547         // there was a change from the old selected and
548         // the new selection
549         if (me.selected.getCount() != toBeSelected.length) {
550             change = true;
551         }
552
553         me.clearSelections();
554
555         if (me.store.indexOf(lastFocused) !== -1) {
556             // restore the last focus but supress restoring focus
557             this.setLastFocused(lastFocused, true);
558         }
559
560         if (toBeSelected.length) {
561             // perform the selection again
562             me.doSelect(toBeSelected, false, true);
563         }
564
565         me.maybeFireSelectionChange(change);
566     },
567
568 <span id='Ext-selection-Model-method-clearSelections'>    /**
569 </span>     * A fast reset of the selections without firing events, updating the ui, etc.
570      * For private usage only.
571      * @private
572      */
573     clearSelections: function() {
574         // reset the entire selection to nothing
575         this.selected.clear();
576         this.lastSelected = null;
577         this.setLastFocused(null);
578     },
579
580     // when a record is added to a store
581     onStoreAdd: function() {
582
583     },
584
585     // when a store is cleared remove all selections
586     // (if there were any)
587     onStoreClear: function() {
588         if (this.selected.getCount &gt; 0) {
589             this.clearSelections();
590             this.maybeFireSelectionChange(true);
591         }
592     },
593
594     // prune records from the SelectionModel if
595     // they were selected at the time they were
596     // removed.
597     onStoreRemove: function(store, record) {
598         var me = this,
599             selected = me.selected;
600
601         if (me.locked || !me.pruneRemoved) {
602             return;
603         }
604
605         if (selected.remove(record)) {
606             if (me.lastSelected == record) {
607                 me.lastSelected = null;
608             }
609             if (me.getLastFocused() == record) {
610                 me.setLastFocused(null);
611             }
612             me.maybeFireSelectionChange(true);
613         }
614     },
615
616 <span id='Ext-selection-Model-method-getCount'>    /**
617 </span>     * Gets the count of selected records.
618      * @return {Number} The number of selected records
619      */
620     getCount: function() {
621         return this.selected.getCount();
622     },
623
624     // cleanup.
625     destroy: function() {
626
627     },
628
629     // if records are updated
630     onStoreUpdate: function() {
631
632     },
633
634     // @abstract
635     onSelectChange: function(record, isSelected, suppressEvent) {
636
637     },
638
639     // @abstract
640     onLastFocusChanged: function(oldFocused, newFocused) {
641
642     },
643
644     // @abstract
645     onEditorKey: function(field, e) {
646
647     },
648
649     // @abstract
650     bindComponent: function(cmp) {
651
652     }
653 });</pre>
654 </body>
655 </html>