-<div id="cls-Ext.data.GroupingStore"></div>/**\r
- * @class Ext.data.GroupingStore\r
- * @extends Ext.data.Store\r
- * A specialized store implementation that provides for grouping records by one of the available fields. This\r
- * is usually used in conjunction with an {@link Ext.grid.GroupingView} to proved the data model for\r
- * a grouped GridPanel.\r
- * @constructor\r
- * Creates a new GroupingStore.\r
- * @param {Object} config A config object containing the objects needed for the Store to access data,\r
- * and read the data into Records.\r
- * @xtype groupingstore\r
- */\r
-Ext.data.GroupingStore = Ext.extend(Ext.data.Store, {\r
- \r
- //inherit docs\r
- constructor: function(config){\r
- Ext.data.GroupingStore.superclass.constructor.call(this, config);\r
- this.applyGroupField();\r
- },\r
- \r
- <div id="cfg-Ext.data.GroupingStore-groupField"></div>/**\r
- * @cfg {String} groupField\r
- * The field name by which to sort the store's data (defaults to '').\r
- */\r
- <div id="cfg-Ext.data.GroupingStore-remoteGroup"></div>/**\r
- * @cfg {Boolean} remoteGroup\r
- * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the\r
- * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a\r
- * helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call.\r
- */\r
- remoteGroup : false,\r
- <div id="cfg-Ext.data.GroupingStore-groupOnSort"></div>/**\r
- * @cfg {Boolean} groupOnSort\r
- * True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the\r
- * existing sort info (defaults to false).\r
- */\r
- groupOnSort:false,\r
-\r
- groupDir : 'ASC',\r
- \r
- <div id="method-Ext.data.GroupingStore-clearGrouping"></div>/**\r
- * Clears any existing grouping and refreshes the data using the default sort.\r
- */\r
- clearGrouping : function(){\r
- this.groupField = false;\r
- if(this.remoteGroup){\r
- if(this.baseParams){\r
- delete this.baseParams.groupBy;\r
- }\r
- var lo = this.lastOptions;\r
- if(lo && lo.params){\r
- delete lo.params.groupBy;\r
- }\r
- this.reload();\r
- }else{\r
- this.applySort();\r
- this.fireEvent('datachanged', this);\r
- }\r
- },\r
-\r
- <div id="method-Ext.data.GroupingStore-groupBy"></div>/**\r
- * Groups the data by the specified field.\r
- * @param {String} field The field name by which to sort the store's data\r
- * @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed\r
- * in is the same as the current grouping field, false to skip grouping on the same field (defaults to false)\r
- */\r
- groupBy : function(field, forceRegroup, direction){\r
- direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir;\r
- if(this.groupField == field && this.groupDir == direction && !forceRegroup){\r
- return; // already grouped by this field\r
- }\r
- this.groupField = field;\r
- this.groupDir = direction;\r
- this.applyGroupField();\r
- if(this.groupOnSort){\r
- this.sort(field, direction);\r
- return;\r
- }\r
- if(this.remoteGroup){\r
- this.reload();\r
- }else{\r
- var si = this.sortInfo || {};\r
- if(si.field != field || si.direction != direction){\r
- this.applySort();\r
- }else{\r
- this.sortData(field, direction);\r
- }\r
- this.fireEvent('datachanged', this);\r
- }\r
- },\r
- \r
- // private\r
- applyGroupField: function(){\r
- if(this.remoteGroup){\r
- if(!this.baseParams){\r
- this.baseParams = {};\r
- }\r
- this.baseParams.groupBy = this.groupField;\r
- this.baseParams.groupDir = this.groupDir;\r
- }\r
- },\r
-\r
- // private\r
- applySort : function(){\r
- Ext.data.GroupingStore.superclass.applySort.call(this);\r
- if(!this.groupOnSort && !this.remoteGroup){\r
- var gs = this.getGroupState();\r
- if(gs && (gs != this.sortInfo.field || this.groupDir != this.sortInfo.direction)){\r
- this.sortData(this.groupField, this.groupDir);\r
- }\r
- }\r
- },\r
-\r
- // private\r
- applyGrouping : function(alwaysFireChange){\r
- if(this.groupField !== false){\r
- this.groupBy(this.groupField, true, this.groupDir);\r
- return true;\r
- }else{\r
- if(alwaysFireChange === true){\r
- this.fireEvent('datachanged', this);\r
- }\r
- return false;\r
- }\r
- },\r
-\r
- // private\r
- getGroupState : function(){\r
- return this.groupOnSort && this.groupField !== false ?\r
- (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField;\r
- }\r
-});\r
-Ext.reg('groupingstore', Ext.data.GroupingStore);</pre>
+<div id="cls-Ext.data.GroupingStore"></div>/**
+ * @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(
+ <div id="event-Ext.data.GroupingStore-groupchange"></div>/**
+ * @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();
+ },
+
+ <div id="cfg-Ext.data.GroupingStore-groupField"></div>/**
+ * @cfg {String} groupField
+ * The field name by which to sort the store's data (defaults to '').
+ */
+ <div id="cfg-Ext.data.GroupingStore-remoteGroup"></div>/**
+ * @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,
+ <div id="cfg-Ext.data.GroupingStore-groupOnSort"></div>/**
+ * @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',
+
+ <div id="method-Ext.data.GroupingStore-clearGrouping"></div>/**
+ * 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);
+ }
+ },
+
+ <div id="method-Ext.data.GroupingStore-groupBy"></div>/**
+ * 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
+ var 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 ? [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);
+</pre>