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