/** * Represents a grouping of items. The grouper works in a similar fashion as the * `Ext.util.Sorter` except that groups must be able to extract a value by which all items * in the group can be collected. By default this is derived from the `property` config * but can be customized using the `groupFn` if necessary. * * All items with the same group value compare as equal. If the group values do not compare * equally, the sort can be controlled further by setting `sortProperty` or `sorterFn`. */Ext.define('Ext.util.Grouper', { extend: 'Ext.util.Sorter', isGrouper: true, config: { /** * @cfg {Function} groupFn This function is called for each item in the collection * to determine the group to which it belongs. By default the `property` value is * used to group items. * @cfg {Object} groupFn.item The current item from the collection. * @cfg {String} groupFn.return The group identifier for the item. */ groupFn: null, /** * @cfg {String} property The field by which records are grouped. Groups are * sorted alphabetically by group value as the default. To sort groups by a different * property, use the {@link #sortProperty} configuration. */ /** * @cfg {String} sortProperty You can set this configuration if you want the groups * to be sorted on something other then the group string returned by the `groupFn`. * This serves the same role as `property` on a normal `Ext.util.Sorter`. */ sortProperty: null, /** * @cfg {String} formatter * This config accepts a format specification as would be used in a `Ext.Template` * formatted token. For example `'round(2)'` to round numbers to 2 decimal places * or `'date("Y-m-d")'` to format a Date. * * It is used to format the group name. Can be used instead of the `groupFn` config. */ formatter: false, /** * @cfg {String} blankValue * * A text that is used if the generated name for the group is empty */ blankValue: '' }, _eventToMethodMap: { propertychange: 'onGrouperPropertyChange', directionchange: 'onGrouperDirectionChange' }, constructor: function(config) { //<debug> if (config) { if (config.getGroupString) { Ext.raise("Cannot set getGroupString - use groupFn instead"); } } //</debug> this.callParent(arguments); }, /** * Returns the string value for grouping, primarily used for grouper key. * @param {Ext.data.Model} item The Model instance * @return {String} */ getGroupString: function(item) { return item.$collapsedGroupPlaceholder ? item.$groupKey : this.getGroupValue(item).toString(); }, /** * Returns the value for grouping to be used. * @param {Ext.data.Model} item The Model instance * @return {Mixed} */ getGroupValue: function(item) { var groupValue = item.$collapsedGroupPlaceholder ? item.$groupValue : this._groupFn(item); return (groupValue != null && groupValue !== '') ? groupValue : this.getBlankValue(); }, sortFn: function(item1, item2) { var me = this, lhs = me.getGroupValue(item1), rhs = me.getGroupValue(item2), property = me._sortProperty, // Sorter's sortFn uses "_property" root = me._root, sorterFn = me._sorterFn, transform = me._transform; // Compare groupFn results for both sides and return if they are equal, ensuring // correct comparison in case values are dates. if (lhs === rhs || Ext.Date.isEqual(lhs, rhs)) { return 0; } if (property || sorterFn) { if (sorterFn) { return sorterFn.call(this, item1, item2); } 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); }, standardGroupFn: function(item) { var me = this, root = me._root, formatter = me._formatter, value = (root ? item[root] : item)[me._property]; if (formatter) { value = formatter(value, me); } return value; }, updateSorterFn: function() { // don't callParent here - we don't want to smash sortFn w/sorterFn }, updateProperty: function(data, oldData) { var me = this; // we don't callParent since that is related to sorterFn smashing sortFn if (!me.getGroupFn()) { me.setGroupFn(me.standardGroupFn); } me.notify('propertychange', [data, oldData]); }, updateDirection: function(data, oldData) { this.callParent([data, oldData]); this.notify('directionchange', [data, oldData]); }, applyFormatter: function(value) { var parser, format; if (!value) { return null; } parser = Ext.app.bind.Parser.fly(value); format = parser.compileFormat(); parser.release(); return function(v, scope) { return format(v, scope); }; }, addObserver: function(observer) { var me = this, observers = me.observers; if (!observers) { me.observers = observers = []; } if (!Ext.Array.contains(observers, observer)) { // if we're in the middle of notifying, we need to clone the observers if (me.notifying) { me.observers = observers = observers.slice(0); } observers[observers.length] = observer; } me.dirtyObservers = true; }, prioritySortFn: function(o1, o2) { var a = +o1.observerPriority, b = +o2.observerPriority; if (isNaN(a)) { a = 0; } if (isNaN(b)) { b = 0; } return a - b; }, removeObserver: function(observer) { var observers = this.observers; if (observers) { Ext.Array.remove(observers, observer); this.dirtyObservers = true; } }, clearObservers: function() { this.observers = null; }, notify: function(eventName, args) { var me = this, observers = me.observers, methodName = me._eventToMethodMap[eventName], added = 0, index, length, method, observer; args = args || []; if (observers && methodName) { me.notifying = true; if (me.dirtyObservers && observers.length > 1) { // Allow observers to be inserted with a priority. // For example GroupCollections must react to Collection mutation before views. // Before notifying our observers let's sort them by priority. Ext.Array.sort(observers, me.prioritySortFn); me.dirtyObservers = false; } for (index = 0, length = observers.length; index < length; ++index) { method = (observer = observers[index])[methodName]; if (method) { if (!added++) { // jshint ignore:line args.unshift(me); } method.apply(observer, args); } } me.notifying = false; } }});