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