X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/2e847cf21b8ab9d15fa167b315ca5b2fa92638fc..6a7e4474cba9d8be4b2ec445e10f1691f7277c50:/src/data/GroupingStore.js diff --git a/src/data/GroupingStore.js b/src/data/GroupingStore.js index 8309a7e3..1cd2a0a1 100644 --- a/src/data/GroupingStore.js +++ b/src/data/GroupingStore.js @@ -1,153 +1,259 @@ /*! - * Ext JS Library 3.1.1 - * Copyright(c) 2006-2010 Ext JS, LLC + * Ext JS Library 3.2.0 + * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ -/** - * @class Ext.data.GroupingStore - * @extends Ext.data.Store - * A specialized store implementation that provides for grouping records by one of the available fields. This - * is usually used in conjunction with an {@link Ext.grid.GroupingView} to proved the data model for - * a grouped GridPanel. - * @constructor - * Creates a new GroupingStore. - * @param {Object} config A config object containing the objects needed for the Store to access data, - * and read the data into Records. - * @xtype groupingstore - */ -Ext.data.GroupingStore = Ext.extend(Ext.data.Store, { - - //inherit docs - constructor: function(config){ - Ext.data.GroupingStore.superclass.constructor.call(this, config); - this.applyGroupField(); - }, - - /** - * @cfg {String} groupField - * The field name by which to sort the store's data (defaults to ''). - */ - /** - * @cfg {Boolean} remoteGroup - * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the - * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a - * helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call. - */ - remoteGroup : false, - /** - * @cfg {Boolean} groupOnSort - * True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the - * existing sort info (defaults to false). - */ - groupOnSort:false, - - groupDir : 'ASC', - - /** - * Clears any existing grouping and refreshes the data using the default sort. - */ - clearGrouping : function(){ - this.groupField = false; - - if(this.remoteGroup){ - if(this.baseParams){ - delete this.baseParams.groupBy; - delete this.baseParams.groupDir; - } - var lo = this.lastOptions; - if(lo && lo.params){ - delete lo.params.groupBy; - delete lo.params.groupDir; - } - - this.reload(); - }else{ - this.applySort(); - this.fireEvent('datachanged', this); - } - }, - - /** - * Groups the data by the specified field. - * @param {String} field The field name by which to sort the store's data - * @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed - * in is the same as the current grouping field, false to skip grouping on the same field (defaults to false) - */ - groupBy : function(field, forceRegroup, direction){ - direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir; - if(this.groupField == field && this.groupDir == direction && !forceRegroup){ - return; // already grouped by this field - } - this.groupField = field; - this.groupDir = direction; - this.applyGroupField(); - if(this.groupOnSort){ - this.sort(field, direction); - return; - } - if(this.remoteGroup){ - this.reload(); - }else{ - var si = this.sortInfo || {}; - if(forceRegroup || si.field != field || si.direction != direction){ - this.applySort(); - }else{ - this.sortData(field, direction); - } - this.fireEvent('datachanged', this); - } - }, - - // private - applyGroupField: function(){ - if(this.remoteGroup){ - if(!this.baseParams){ - this.baseParams = {}; - } - Ext.apply(this.baseParams, { - groupBy : this.groupField, - groupDir : this.groupDir - }); - - var lo = this.lastOptions; - if(lo && lo.params){ - Ext.apply(lo.params, { - groupBy : this.groupField, - groupDir : this.groupDir - }); - } - } - }, - - // private - applySort : function(){ - Ext.data.GroupingStore.superclass.applySort.call(this); - if(!this.groupOnSort && !this.remoteGroup){ - var gs = this.getGroupState(); - if(gs && (gs != this.sortInfo.field || this.groupDir != this.sortInfo.direction)){ - this.sortData(this.groupField, this.groupDir); - } - } - }, - - // private - applyGrouping : function(alwaysFireChange){ - if(this.groupField !== false){ - this.groupBy(this.groupField, true, this.groupDir); - return true; - }else{ - if(alwaysFireChange === true){ - this.fireEvent('datachanged', this); - } - return false; - } - }, - - // private - getGroupState : function(){ - return this.groupOnSort && this.groupField !== false ? - (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField; - } -}); -Ext.reg('groupingstore', Ext.data.GroupingStore); \ No newline at end of file +/** + * @class Ext.data.GroupingStore + * @extends Ext.data.Store + * A specialized store implementation that provides for grouping records by one of the available fields. This + * is usually used in conjunction with an {@link Ext.grid.GroupingView} to provide the data model for + * a grouped GridPanel. + * + * Internally, GroupingStore is simply a normal Store with multi sorting enabled from the start. The grouping field + * and direction are always injected as the first sorter pair. GroupingView picks up on the configured groupField and + * builds grid rows appropriately. + * + * @constructor + * Creates a new GroupingStore. + * @param {Object} config A config object containing the objects needed for the Store to access data, + * and read the data into Records. + * @xtype groupingstore + */ +Ext.data.GroupingStore = Ext.extend(Ext.data.Store, { + + //inherit docs + constructor: function(config) { + config = config || {}; + + //We do some preprocessing here to massage the grouping + sorting options into a single + //multi sort array. If grouping and sorting options are both presented to the constructor, + //the sorters array consists of the grouping sorter object followed by the sorting sorter object + //see Ext.data.Store's sorting functions for details about how multi sorting works + this.hasMultiSort = true; + this.multiSortInfo = this.multiSortInfo || {sorters: []}; + + var sorters = this.multiSortInfo.sorters, + groupField = config.groupField || this.groupField, + sortInfo = config.sortInfo || this.sortInfo, + groupDir = config.groupDir || this.groupDir; + + //add the grouping sorter object first + if(groupField){ + sorters.push({ + field : groupField, + direction: groupDir + }); + } + + //add the sorting sorter object if it is present + if (sortInfo) { + sorters.push(sortInfo); + } + + Ext.data.GroupingStore.superclass.constructor.call(this, config); + + this.addEvents( + /** + * @event groupchange + * Fired whenever a call to store.groupBy successfully changes the grouping on the store + * @param {Ext.data.GroupingStore} store The grouping store + * @param {String} groupField The field that the store is now grouped by + */ + 'groupchange' + ); + + this.applyGroupField(); + }, + + /** + * @cfg {String} groupField + * The field name by which to sort the store's data (defaults to ''). + */ + /** + * @cfg {Boolean} remoteGroup + * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the + * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a + * helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call. + */ + remoteGroup : false, + /** + * @cfg {Boolean} groupOnSort + * True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the + * existing sort info (defaults to false). + */ + groupOnSort:false, + + groupDir : 'ASC', + + /** + * Clears any existing grouping and refreshes the data using the default sort. + */ + clearGrouping : function(){ + this.groupField = false; + + if(this.remoteGroup){ + if(this.baseParams){ + delete this.baseParams.groupBy; + delete this.baseParams.groupDir; + } + var lo = this.lastOptions; + if(lo && lo.params){ + delete lo.params.groupBy; + delete lo.params.groupDir; + } + + this.reload(); + }else{ + this.sort(); + this.fireEvent('datachanged', this); + } + }, + + /** + * Groups the data by the specified field. + * @param {String} field The field name by which to sort the store's data + * @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed + * in is the same as the current grouping field, false to skip grouping on the same field (defaults to false) + */ + groupBy : function(field, forceRegroup, direction) { + direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir; + + if (this.groupField == field && this.groupDir == direction && !forceRegroup) { + return; // already grouped by this field + } + + //check the contents of the first sorter. If the field matches the CURRENT groupField (before it is set to the new one), + //remove the sorter as it is actually the grouper. The new grouper is added back in by this.sort + sorters = this.multiSortInfo.sorters; + if (sorters.length > 0 && sorters[0].field == this.groupField) { + sorters.shift(); + } + + this.groupField = field; + this.groupDir = direction; + this.applyGroupField(); + + var fireGroupEvent = function() { + this.fireEvent('groupchange', this, this.getGroupState()); + }; + + if (this.groupOnSort) { + this.sort(field, direction); + fireGroupEvent.call(this); + return; + } + + if (this.remoteGroup) { + this.on('load', fireGroupEvent, this, {single: true}); + this.reload(); + } else { + this.sort(sorters); + fireGroupEvent.call(this); + } + }, + + //GroupingStore always uses multisorting so we intercept calls to sort here to make sure that our grouping sorter object + //is always injected first. + sort : function(fieldName, dir) { + if (this.remoteSort) { + return Ext.data.GroupingStore.superclass.sort.call(this, fieldName, dir); + } + + var sorters = []; + + //cater for any existing valid arguments to this.sort, massage them into an array of sorter objects + if (Ext.isArray(arguments[0])) { + sorters = arguments[0]; + } else if (fieldName == undefined) { + //we preserve the existing sortInfo here because this.sort is called after + //clearGrouping and there may be existing sorting + sorters = [this.sortInfo]; + } else { + //TODO: this is lifted straight from Ext.data.Store's singleSort function. It should instead be + //refactored into a common method if possible + var field = this.fields.get(fieldName); + if (!field) return false; + + var name = field.name, + sortInfo = this.sortInfo || null, + sortToggle = this.sortToggle ? this.sortToggle[name] : null; + + if (!dir) { + if (sortInfo && sortInfo.field == name) { // toggle sort dir + dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC'); + } else { + dir = field.sortDir; + } + } + + this.sortToggle[name] = dir; + this.sortInfo = {field: name, direction: dir}; + + sorters = [this.sortInfo]; + } + + //add the grouping sorter object as the first multisort sorter + if (this.groupField) { + sorters.unshift({direction: this.groupDir, field: this.groupField}); + } + + return this.multiSort.call(this, sorters, dir); + }, + + /** + * @private + * Saves the current grouping field and direction to this.baseParams and this.lastOptions.params + * if we're using remote grouping. Does not actually perform any grouping - just stores values + */ + applyGroupField: function(){ + if (this.remoteGroup) { + if(!this.baseParams){ + this.baseParams = {}; + } + + Ext.apply(this.baseParams, { + groupBy : this.groupField, + groupDir: this.groupDir + }); + + var lo = this.lastOptions; + if (lo && lo.params) { + lo.params.groupDir = this.groupDir; + + //this is deleted because of a bug reported at http://www.extjs.com/forum/showthread.php?t=82907 + delete lo.params.groupBy; + } + } + }, + + /** + * @private + * TODO: This function is apparently never invoked anywhere in the framework. It has no documentation + * and should be considered for deletion + */ + applyGrouping : function(alwaysFireChange){ + if(this.groupField !== false){ + this.groupBy(this.groupField, true, this.groupDir); + return true; + }else{ + if(alwaysFireChange === true){ + this.fireEvent('datachanged', this); + } + return false; + } + }, + + /** + * @private + * Returns the grouping field that should be used. If groupOnSort is used this will be sortInfo's field, + * otherwise it will be this.groupField + * @return {String} The group field + */ + getGroupState : function(){ + return this.groupOnSort && this.groupField !== false ? + (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField; + } +}); +Ext.reg('groupingstore', Ext.data.GroupingStore);