Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / util / Sortable.js
1 /**
2  * @class Ext.util.Sortable
3
4 A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
5
6 **NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
7 is more likely you will want to use one of the component classes that import this mixin, such as
8 {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
9  * @markdown
10  * @docauthor Tommy Maintz <tommy@sencha.com>
11  */
12 Ext.define("Ext.util.Sortable", {
13     /**
14      * @property isSortable
15      * @type Boolean
16      * Flag denoting that this object is sortable. Always true.
17      */
18     isSortable: true,
19     
20     /**
21      * The default sort direction to use if one is not specified (defaults to "ASC")
22      * @property defaultSortDirection
23      * @type String
24      */
25     defaultSortDirection: "ASC",
26     
27     requires: [
28         'Ext.util.Sorter'
29     ],
30
31     /**
32      * The property in each item that contains the data to sort. (defaults to null)
33      * @type String
34      */    
35     sortRoot: null,
36     
37     /**
38      * Performs initialization of this mixin. Component classes using this mixin should call this method
39      * during their own initialization.
40      */
41     initSortable: function() {
42         var me = this,
43             sorters = me.sorters;
44         
45         /**
46          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
47          * @property sorters
48          * @type Ext.util.MixedCollection
49          */
50         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
51             return item.id || item.property;
52         });
53         
54         if (sorters) {
55             me.sorters.addAll(me.decodeSorters(sorters));
56         }
57     },
58
59     /**
60      * <p>Sorts the data in the Store by one or more of its properties. Example usage:</p>
61 <pre><code>
62 //sort by a single field
63 myStore.sort('myField', 'DESC');
64
65 //sorting by multiple fields
66 myStore.sort([
67     {
68         property : 'age',
69         direction: 'ASC'
70     },
71     {
72         property : 'name',
73         direction: 'DESC'
74     }
75 ]);
76 </code></pre>
77      * <p>Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates the actual
78      * sorting to its internal {@link Ext.util.MixedCollection}.</p>
79      * <p>When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:</p>
80 <pre><code>
81 store.sort('myField');
82 store.sort('myField');
83      </code></pre>
84      * <p>Is equivalent to this code, because Store handles the toggling automatically:</p>
85 <pre><code>
86 store.sort('myField', 'ASC');
87 store.sort('myField', 'DESC');
88 </code></pre>
89      * @param {String|Array} sorters Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
90      * or an Array of sorter configurations.
91      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
92      */
93     sort: function(sorters, direction, where, doSort) {
94         var me = this,
95             sorter, sorterFn,
96             newSorters;
97         
98         if (Ext.isArray(sorters)) {
99             doSort = where;
100             where = direction;
101             newSorters = sorters;
102         }
103         else if (Ext.isObject(sorters)) {
104             doSort = where;
105             where = direction;
106             newSorters = [sorters];
107         }
108         else if (Ext.isString(sorters)) {
109             sorter = me.sorters.get(sorters);
110
111             if (!sorter) {
112                 sorter = {
113                     property : sorters,
114                     direction: direction
115                 };
116                 newSorters = [sorter];
117             }
118             else if (direction === undefined) {
119                 sorter.toggle();
120             }
121             else {
122                 sorter.setDirection(direction);
123             }
124         }
125         
126         if (newSorters && newSorters.length) {
127             newSorters = me.decodeSorters(newSorters);
128             if (Ext.isString(where)) {
129                 if (where === 'prepend') {
130                     sorters = me.sorters.clone().items;
131                     
132                     me.sorters.clear();
133                     me.sorters.addAll(newSorters);
134                     me.sorters.addAll(sorters);
135                 }
136                 else {
137                     me.sorters.addAll(newSorters);
138                 }
139             }
140             else {
141                 me.sorters.clear();
142                 me.sorters.addAll(newSorters);
143             }
144             
145             if (doSort !== false) {
146                 me.onBeforeSort(newSorters);
147             }
148         }
149         
150         if (doSort !== false) {
151             sorters = me.sorters.items;
152             if (sorters.length) {
153                 //construct an amalgamated sorter function which combines all of the Sorters passed
154                 sorterFn = function(r1, r2) {
155                     var result = sorters[0].sort(r1, r2),
156                         length = sorters.length,
157                         i;
158
159                         //if we have more than one sorter, OR any additional sorter functions together
160                         for (i = 1; i < length; i++) {
161                             result = result || sorters[i].sort.call(this, r1, r2);
162                         }
163
164                     return result;
165                 };
166
167                 me.doSort(sorterFn);                
168             }
169         }
170         
171         return sorters;
172     },
173     
174     onBeforeSort: Ext.emptyFn,
175         
176     /**
177      * @private
178      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
179      * @param {Array} sorters The sorters array
180      * @return {Array} Array of Ext.util.Sorter objects
181      */
182     decodeSorters: function(sorters) {
183         if (!Ext.isArray(sorters)) {
184             if (sorters === undefined) {
185                 sorters = [];
186             } else {
187                 sorters = [sorters];
188             }
189         }
190
191         var length = sorters.length,
192             Sorter = Ext.util.Sorter,
193             fields = this.model ? this.model.prototype.fields : null,
194             field,
195             config, i;
196
197         for (i = 0; i < length; i++) {
198             config = sorters[i];
199
200             if (!(config instanceof Sorter)) {
201                 if (Ext.isString(config)) {
202                     config = {
203                         property: config
204                     };
205                 }
206                 
207                 Ext.applyIf(config, {
208                     root     : this.sortRoot,
209                     direction: "ASC"
210                 });
211
212                 //support for 3.x style sorters where a function can be defined as 'fn'
213                 if (config.fn) {
214                     config.sorterFn = config.fn;
215                 }
216
217                 //support a function to be passed as a sorter definition
218                 if (typeof config == 'function') {
219                     config = {
220                         sorterFn: config
221                     };
222                 }
223
224                 // ensure sortType gets pushed on if necessary
225                 if (fields && !config.transform) {
226                     field = fields.get(config.property);
227                     config.transform = field ? field.sortType : undefined;
228                 }
229                 sorters[i] = Ext.create('Ext.util.Sorter', config);
230             }
231         }
232
233         return sorters;
234     },
235     
236     getSorters: function() {
237         return this.sorters.items;
238     }
239 });