    /**
     * A small abstract class that contains the shared behaviour for any summary
     * calculations to be used in the grid.
     */
    Ext.define('Ext.grid.feature.AbstractSummary', {

        extend: 'Ext.grid.feature.Feature',

        alias: 'feature.abstractsummary',

        summaryRowCls: Ext.baseCSSPrefix + 'grid-row-summary',
        summaryRowSelector: '.' + Ext.baseCSSPrefix + 'grid-row-summary',

        // High priority rowTpl interceptor which sees summary rows early, and renders them correctly and then aborts the row rendering chain.
        // This will only see action when summary rows are being updated and Table.onUpdate->Table.bufferRender renders the individual updated sumary row.
        summaryRowTpl: {
            before: function(values, out) {
                // If a summary record comes through the rendering pipeline, render it simply, and return false from the
                // before method which aborts the tpl chain
                if (values.record.isSummary) {
                    this.summaryFeature.outputSummaryRecord(values.record, values, out);
                    return false;
                }
            },
            priority: 1000
        },

       /**
        * @cfg {Boolean}
        * True to show the summary row.
        */
        showSummaryRow: true,

        // Listen for store updates. Eg, from an Editor.
        init: function() {
            var me = this;
            me.view.summaryFeature = me;
            me.rowTpl = me.view.self.prototype.rowTpl;

            // Add a high priority interceptor which renders summary records simply
            // This will only see action ona bufferedRender situation where summary records are updated.
            me.view.addRowTpl(me.summaryRowTpl).summaryFeature = me;
        },

        /**
         * Toggle whether or not to show the summary row.
         * @param {Boolean} visible True to show the summary row
         */
        toggleSummaryRow: function(visible) {
            this.showSummaryRow = !!visible;
        },

        outputSummaryRecord: function(summaryRecord, contextValues, out) {
            var view = contextValues.view,
                savedRowValues = view.rowValues,
                columns = contextValues.columns || view.headerCt.getGridColumns(),
                colCount = columns.length, i, column,
                // Set up a row rendering values object so that we can call the rowTpl directly to inject
                // the markup of a grid row into the output stream.
                values = {
                    view: view,
                    record: summaryRecord,
                    rowStyle: '',
                    rowClasses: [ this.summaryRowCls ],
                    itemClasses: [],
                    recordIndex: -1,
                    rowId: view.id + '-record-' + summaryRecord.internalId,
                    columns: columns,
                    visibleColumns: contextValues.visibleColumns || view.headerCt.getVisibleGridColumns()
                };

            // Because we are using the regular row rendering pathway, temporarily swap out the renderer for the summaryRenderer
            for (i = 0; i < colCount; i++) {
                column = columns[i];
                column.savedRenderer = column.renderer;
                if (column.summaryRenderer) {
                    column.renderer = column.summaryRenderer;
                } else if (!column.summaryType) {
                    column.renderer = Ext.emptyFn;
                }

                // Summary records may contain values based upon the column's ID if the column is not mapped from a field
                if (!column.dataIndex) {
                    column.dataIndex = column.id;
                }
            }

            // Use the base template to render a summary row
            view.rowValues = values;
            view.self.prototype.rowTpl.applyOut(values, out);
            view.rowValues = savedRowValues;

            // Restore regular column renderers
            for (i = 0; i < colCount; i++) {
                column = columns[i];
                column.renderer = column.savedRenderer;
                column.savedRenderer = null;
            }
        },

        /**
         * Get the summary data for a field.
         * @private
         * @param {Ext.data.Store} store The store to get the data from
         * @param {String/Function} type The type of aggregation. If a function is specified it will
         * be passed to the stores aggregate function.
         * @param {String} field The field to aggregate on
         * @param {Boolean} group True to aggregate in grouped mode
         * @return {Number/String/Object} See the return type for the store functions.
         * if the group parameter is `true` An object is returned with a property named for each group who's
         * value is the summary value.
         */
        getSummary: function(store, type, field, group){
            var records = group.records;

            if (type) {
                if (Ext.isFunction(type)) {
                    return store.getAggregate(type, null, records, [field]);
                }

                switch (type) {
                    case 'count':
                        return records.length;
                    case 'min':
                        return store.getMin(records, field);
                    case 'max':
                        return store.getMax(records, field);
                    case 'sum':
                        return store.getSum(records, field);
                    case 'average':
                        return store.getAverage(records, field);
                    default:
                        return '';

                }
            }
        },

        /**
         * Used by the Grouping Feature when {@link #showSummaryRow} is `true`.
         *
         * Generates group summary data for the whole store.
         * @private
         * @return {Object} An object hash keyed by group name containing summary records.
         */
        generateSummaryData: function(){
            var me = this,
                store = me.view.store,
                groups = store.groups.items,
                reader = store.proxy.reader,
                len = groups.length,
                groupField = me.getGroupField(),
                data = {},
                lockingPartner = me.lockingPartner,
                i, group, record,
                root, summaryRows, hasRemote,
                convertedSummaryRow, remoteData;

            /**
             * @cfg {String} [remoteRoot=undefined]
             * The name of the property which contains the Array of summary objects.
             * It allows to use server-side calculated summaries.
             */
            if (me.remoteRoot && reader.rawData) {
                hasRemote = true;
                remoteData = {};
                // reset reader root and rebuild extractors to extract summaries data
                root = reader.root;
                reader.root = me.remoteRoot;
                reader.buildExtractors(true);
                summaryRows = reader.getRoot(reader.rawData);
                len = summaryRows.length;

                // Ensure the Reader has a data conversion function to convert a raw data row into a Record data hash
                if (!reader.convertRecordData) {
                    reader.buildExtractors();
                }

                for (i = 0; i < len; ++i) {
                    convertedSummaryRow = {};

                    // Convert a raw data row into a Record's hash object using the Reader
                    reader.convertRecordData(convertedSummaryRow, summaryRows[i]);
                    remoteData[convertedSummaryRow[groupField]] = convertedSummaryRow;
                }

                // restore initial reader configuration
                reader.root = root;
                reader.buildExtractors(true);
            }

            for (i = 0; i < len; ++i) {
                group = groups[i];
                // Something has changed or it doesn't exist, populate it
                if (hasRemote || group.isDirty() || !group.hasAggregate()) {
                    if (hasRemote) {
                        record = me.populateRemoteRecord(group, remoteData);
                    } else {
                        record = me.populateRecord(group);
                    }
                    // Clear the dirty state of the group if this is the only Summary, or this is the right hand (normal grid's) summary
                    if (!lockingPartner || (me.view.ownerCt === me.view.ownerCt.ownerLockable.normalGrid)) {
                        group.commit();
                    }
                } else {
                    record = group.getAggregateRecord();
                    if (lockingPartner && !record.hasPartnerData) {
                        me.populateRecord(group);
                        record.hasPartnerData = true;
                    }
                }
                data[group.key] = record;
            }

            return data;
        },

        populateRemoteRecord: function(group, data) {
            var record = group.getAggregateRecord(true),
                groupData = data[group.key],
                field;

            record.beginEdit();
            for (field in groupData) {
                if (groupData.hasOwnProperty(field)) {
                    record.set(field, groupData[field]);
                }
            }
            record.endEdit(true);
            record.commit(true);

            return record;
        },

        populateRecord: function(group){
            var me = this,
                view = me.view,
                store = view.store,
                record = group.getAggregateRecord(),
                columns = view.headerCt.getGridColumns(),
                len = columns.length,
                i, column, fieldName;

            record.beginEdit();
            for (i = 0; i < len; ++i) {
                column = columns[i];
                // Use the column id if there's no mapping, could be a calculated field
                fieldName = column.dataIndex || column.id;
                record.set(fieldName, me.getSummary(store, column.summaryType, fieldName, group));
            }
            record.endEdit(true);
            record.commit();

            return record;
        }
    });