    /** * 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: "Tina Belcher"}, *               { 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: "The Cougar"} *           ] *        }); *         *        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()        };    }});