/** * Represents a single sorter that can be used as part of the sorters configuration in * Ext.mixin.Sortable. * * A common place for Sorters to be used are {@link Ext.data.Store Stores}. For example: * * @example * var store = Ext.create('Ext.data.Store', { * fields: ['firstName', 'level'], * sorters: 'level', * * data: [ * { firstName: 'Mitch', level: 9000}, * { firstName: 'Seth', level: 42}, * { firstName: 'Fred', level: 510}, * { firstName: 'Israel', level: 690}, * { firstName: 'Greg', level: 101}, * { firstName: 'Pat', level: 0}, * { firstName: 'Kevin', level: 17}, * { firstName: 'Brandon',level: 690}, * { firstName: 'Gary', level: 409}, * { firstName: 'Scott', level: 789} * ] * }); * * Ext.create('Ext.grid.Panel', { * title: 'Support', * store: store, * columns: [ * { text: 'Name', dataIndex: 'firstName' }, * { text: 'Level', dataIndex: 'level' } * ], * height: 300, * width: 200, * renderTo: Ext.getBody() * }); * * In the next example, we specify a custom sorter function: * * @example * var store = Ext.create('Ext.data.Store', { * fields: ['firstName', 'spiritAnimal'], * sorters: [ * { * // Sort by first letter of second word of spirit animal, in * // descending order * sorterFn: function(record1, record2) { * var name1 = record1.data.spiritAnimal.split(' ')[1].substr(0,1), * name2 = record2.data.spiritAnimal.split(' ')[1].substr(0,1); * * return name1 > name2 ? 1 : (name1 === name2) ? 0 : -1; * }, * direction: 'DESC' * } * ], * * data: [ * { firstName: 'Mitch', spiritAnimal: "Panda Bear"}, * { firstName: 'Seth', spiritAnimal: "Rascally Rabbit"}, * { firstName: 'Fred', spiritAnimal: "Honey Badger"}, * { firstName: 'Israel', spiritAnimal: "Mysterious Capybara"}, * { firstName: 'Greg', spiritAnimal: "Majestic Platypus"}, * { firstName: 'Kevin', spiritAnimal: "Sparkling Unicorn"}, * { firstName: 'Brandon',spiritAnimal: "Pygmy Goat"}, * { firstName: 'Gary', spiritAnimal: "Suri Alpaca"}, * { firstName: 'Scott', spiritAnimal: "Ripe Armadillo"}, * { firstName: 'Pat', spiritAnimal: "Wiley Coyote"} * ] * }); * * Ext.create('Ext.grid.Panel', { * title: 'Support', * store: store, * columns: [ * { text: 'Name', dataIndex: 'firstName' }, * { text: 'Spirit Animal', dataIndex: 'spiritAnimal', flex: 1 } * ], * height: 310, * renderTo: Ext.getBody() * }); */Ext.define('Ext.util.Sorter', { isSorter: true, config: { /** * @cfg {String} property * The property to sort by. Required unless `sorterFn` is provided */ property: null, /** * @cfg {Function} sorterFn * A specific sorter function to execute. Can be passed instead of {@link #property}. * This function should compare the two passed arguments, returning -1, 0 or 1 * depending on if item 1 should be sorted before, at the same level, or after * item 2. * * sorterFn: function(person1, person2) { * return (person1.age > person2.age) ? 1 : (person1.age === person2.age ? 0 : -1); * } */ sorterFn: null, /** * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, * in which case we set the root to 'data' to make the filter pull the {@link #property} * out of the data object of each item */ root: null, /** * @cfg {Function} transform A function that will be run on each value before * it is compared in the sorter. The function will receive a single argument, * the value. */ transform: null, /** * @cfg {String} direction The direction to sort by. Valid values are "ASC", and "DESC". */ direction: "ASC", /** * @cfg {Mixed} id An optional id this sorter can be keyed by in Collections. If * no id is specified it will use the property name used in this Sorter. If no * property is specified, e.g. when adding a custom sorter function we will generate * a random id. */ id: undefined }, statics: { /** * Creates a comparator function (a function that can be passed to `Array.sort`) * given one or more `Sorter` instances. * * The returned function retains a reference to the collection or array of sorters * passed. This means the function will produce a comparison based on the current * content of the collection or array, and not based on the content at the time of * this call. * * @param {Ext.util.Sorter[]/Ext.util.Collection} sorters The `Sorter` instances. * @param [nextFn] The next comparator function to call if all the `sorters` end * with equality. * @return {Function} The comparator function. */ createComparator: function(sorters, nextFn) { nextFn = nextFn || 0; return function(lhs, rhs) { var items = sorters.isCollection ? sorters.items : sorters, n = items.length, comp, i; for (i = 0; i < n; ++i) { comp = items[i].sort(lhs, rhs); if (comp) { return comp; } } return nextFn && nextFn(lhs, rhs); }; } }, /** * This value is set based on the `direction` config to be either 1 or -1. This is used * as a multiplier for the raw comparison value to factor in the direction. * @private * @readonly */ multiplier: 1, constructor: function(config) { //<debug> if (config && !this.isGrouper) { if (!config.property === !config.sorterFn) { // the above is a "not XOR" - both true or both false Ext.raise("A Sorter requires either a property or a sorterFn."); } } //</debug> this.initConfig(config); }, getId: function() { var id = this._id; if (!id) { id = this.getProperty(); if (!id) { id = Ext.id(null, 'ext-sorter-'); } this._id = id; } return id; }, sort: function(lhs, rhs) { return this.multiplier * this.sortFn(lhs, rhs); }, /** * @private * Basic default sorter function that just compares the defined property of each object. * This is hidden by the `sorterFn` provided by the user. */ sortFn: function(item1, item2) { var me = this, transform = me._transform, root = me._root, property = me._property, lhs, rhs; if (root) { item1 = item1[root]; item2 = item2[root]; } lhs = item1[property]; rhs = item2[property]; if (transform) { lhs = transform(lhs); rhs = transform(rhs); } return (lhs > rhs) ? 1 : (lhs < rhs ? -1 : 0); }, applyDirection: function(direction) { return direction ? direction : 'ASC'; }, updateDirection: function(direction) { this.multiplier = (direction.toUpperCase() === "DESC") ? -1 : 1; }, updateProperty: function(property) { if (property) { // Unhide the default sortFn on our prototype delete this.sortFn; } }, updateSorterFn: function(sorterFn) { // Hide the default sortFn on our prototype this.sortFn = sorterFn; }, /** * Toggles the direction of this Sorter. Note that when you call this function, * the Collection this Sorter is part of does not get refreshed automatically. */ toggle: function() { this.setDirection(Ext.String.toggle(this.getDirection(), "ASC", "DESC")); }, /** * Returns this sorter's state. * @return {Object} */ getState: function() { var me = this, result = { root: me.getRoot(), property: me.getProperty(), direction: me.getDirection() }; // Do not use getId() which will create an identifier if we have none. // We need to know if we really are identifiable. if (me._id) { result.id = me._id; } return result; }, /** * Returns this sorter's serialized state. This is used when transmitting this sorter * to a server. * @return {Object} */ serialize: function() { return { property: this.getProperty(), direction: this.getDirection() }; }});