Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / examples / ux / GroupSummary.js
index 0cfdd44..46267db 100644 (file)
 /*!
- * 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
  */
-Ext.ns('Ext.ux.grid');\r
-\r
-/**\r
- * @class Ext.ux.grid.GroupSummary\r
- * @extends Ext.util.Observable\r
- * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
- * updated grouped summary row.\r
- */\r
-Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
-    /**\r
-     * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
-summaryRenderer: function(v, params, data){\r
-    return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
-},\r
-     * </code></pre>\r
-     */\r
-    /**\r
-     * @cfg {String} summaryType (Optional) The type of\r
-     * calculation to be used for the column.  For options available see\r
-     * {@link #Calculations}.\r
-     */\r
-\r
-    constructor : function(config){\r
-        Ext.apply(this, config);\r
-        Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
-    },\r
-    init : function(grid){\r
-        this.grid = grid;\r
-        var v = this.view = grid.getView();\r
-        v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
-\r
-        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
-        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
-        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
-        v.afterMethod('onUpdate', this.doUpdate, this);\r
-        v.afterMethod('onRemove', this.doRemove, this);\r
-\r
-        if(!this.rowTpl){\r
-            this.rowTpl = new Ext.Template(\r
-                '<div class="x-grid3-summary-row" style="{tstyle}">',\r
-                '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
-                    '<tbody><tr>{cells}</tr></tbody>',\r
-                '</table></div>'\r
-            );\r
-            this.rowTpl.disableFormats = true;\r
-        }\r
-        this.rowTpl.compile();\r
-\r
-        if(!this.cellTpl){\r
-            this.cellTpl = new Ext.Template(\r
-                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
-                '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
-                "</td>"\r
-            );\r
-            this.cellTpl.disableFormats = true;\r
-        }\r
-        this.cellTpl.compile();\r
-    },\r
-\r
-    /**\r
-     * Toggle the display of the summary row on/off\r
-     * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
-     */\r
-    toggleSummaries : function(visible){\r
-        var el = this.grid.getGridEl();\r
-        if(el){\r
-            if(visible === undefined){\r
-                visible = el.hasClass('x-grid-hide-summary');\r
-            }\r
-            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
-        }\r
-    },\r
-\r
-    renderSummary : function(o, cs){\r
-        cs = cs || this.view.getColumnData();\r
-        var cfg = this.grid.getColumnModel().config,\r
-            buf = [], c, p = {}, cf, last = cs.length-1;\r
-        for(var i = 0, len = cs.length; i < len; i++){\r
-            c = cs[i];\r
-            cf = cfg[i];\r
-            p.id = c.id;\r
-            p.style = c.style;\r
-            p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
-            if(cf.summaryType || cf.summaryRenderer){\r
-                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
-            }else{\r
-                p.value = '';\r
-            }\r
-            if(p.value == undefined || p.value === "") p.value = "&#160;";\r
-            buf[buf.length] = this.cellTpl.apply(p);\r
-        }\r
-\r
-        return this.rowTpl.apply({\r
-            tstyle: 'width:'+this.view.getTotalWidth()+';',\r
-            cells: buf.join('')\r
-        });\r
-    },\r
-\r
-    /**\r
-     * @private\r
-     * @param {Object} rs\r
-     * @param {Object} cs\r
-     */\r
-    calculate : function(rs, cs){\r
-        var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;\r
-        for(var j = 0, jlen = rs.length; j < jlen; j++){\r
-            r = rs[j];\r
-            for(var i = 0, len = cs.length; i < len; i++){\r
-                c = cs[i];\r
-                cf = cfg[i];\r
-                if(cf.summaryType){\r
-                    data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
-                }\r
-            }\r
-        }\r
-        return data;\r
-    },\r
-\r
-    doGroupEnd : function(buf, g, cs, ds, colCount){\r
-        var data = this.calculate(g.rs, cs);\r
-        buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
-    },\r
-\r
-    doWidth : function(col, w, tw){\r
-        var gs = this.view.getGroups(), s;\r
-        for(var i = 0, len = gs.length; i < len; i++){\r
-            s = gs[i].childNodes[2];\r
-            s.style.width = tw;\r
-            s.firstChild.style.width = tw;\r
-            s.firstChild.rows[0].childNodes[col].style.width = w;\r
-        }\r
-    },\r
-\r
-    doAllWidths : function(ws, tw){\r
-        var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
-        for(var i = 0, len = gs.length; i < len; i++){\r
-            s = gs[i].childNodes[2];\r
-            s.style.width = tw;\r
-            s.firstChild.style.width = tw;\r
-            cells = s.firstChild.rows[0].childNodes;\r
-            for(var j = 0; j < wlen; j++){\r
-                cells[j].style.width = ws[j];\r
-            }\r
-        }\r
-    },\r
-\r
-    doHidden : function(col, hidden, tw){\r
-        var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
-        for(var i = 0, len = gs.length; i < len; i++){\r
-            s = gs[i].childNodes[2];\r
-            s.style.width = tw;\r
-            s.firstChild.style.width = tw;\r
-            s.firstChild.rows[0].childNodes[col].style.display = display;\r
-        }\r
-    },\r
-\r
-    // Note: requires that all (or the first) record in the\r
-    // group share the same group value. Returns false if the group\r
-    // could not be found.\r
-    refreshSummary : function(groupValue){\r
-        return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
-    },\r
-\r
-    getSummaryNode : function(gid){\r
-        var g = Ext.fly(gid, '_gsummary');\r
-        if(g){\r
-            return g.down('.x-grid3-summary-row', true);\r
-        }\r
-        return null;\r
-    },\r
-\r
-    refreshSummaryById : function(gid){\r
-        var g = Ext.getDom(gid);\r
-        if(!g){\r
-            return false;\r
-        }\r
-        var rs = [];\r
-        this.grid.getStore().each(function(r){\r
-            if(r._groupId == gid){\r
-                rs[rs.length] = r;\r
-            }\r
-        });\r
-        var cs = this.view.getColumnData(),\r
-            data = this.calculate(rs, cs),\r
-            markup = this.renderSummary({data: data}, cs),\r
-            existing = this.getSummaryNode(gid);\r
-            \r
-        if(existing){\r
-            g.removeChild(existing);\r
-        }\r
-        Ext.DomHelper.append(g, markup);\r
-        return true;\r
-    },\r
-\r
-    doUpdate : function(ds, record){\r
-        this.refreshSummaryById(record._groupId);\r
-    },\r
-\r
-    doRemove : function(ds, record, index, isUpdate){\r
-        if(!isUpdate){\r
-            this.refreshSummaryById(record._groupId);\r
-        }\r
-    },\r
-\r
-    /**\r
-     * Show a message in the summary row.\r
-     * <pre><code>\r
-grid.on('afteredit', function(){\r
-    var groupValue = 'Ext Forms: Field Anchoring';\r
-    summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
-});\r
-     * </code></pre>\r
-     * @param {String} groupValue\r
-     * @param {String} msg Text to use as innerHTML for the summary row.\r
-     */\r
-    showSummaryMsg : function(groupValue, msg){\r
-        var gid = this.view.getGroupId(groupValue),\r
-             node = this.getSummaryNode(gid);\r
-        if(node){\r
-            node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
-        }\r
-    }\r
-});\r
-\r
-//backwards compat\r
-Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
-\r
-\r
-/**\r
- * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
- * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
- * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
- * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
- * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
- * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
- * </ul></div>\r
- * <p>Custom calculations may be implemented.  An example of\r
- * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
-// define a custom summary function\r
-Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
-    return v + (record.data.estimate * record.data.rate);\r
-};\r
- * </code></pre>\r
- * @property Calculations\r
- */\r
-\r
-Ext.ux.grid.GroupSummary.Calculations = {\r
-    'sum' : function(v, record, field){\r
-        return v + (record.data[field]||0);\r
-    },\r
-\r
-    'count' : function(v, record, field, data){\r
-        return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
-    },\r
-\r
-    'max' : function(v, record, field, data){\r
-        var v = record.data[field];\r
-        var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
-        return v > max ? (data[field+'max'] = v) : max;\r
-    },\r
-\r
-    'min' : function(v, record, field, data){\r
-        var v = record.data[field];\r
-        var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
-        return v < min ? (data[field+'min'] = v) : min;\r
-    },\r
-\r
-    'average' : function(v, record, field, data){\r
-        var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
-        var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
-        return t === 0 ? 0 : t / c;\r
-    }\r
-};\r
-Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
-\r
-/**\r
- * @class Ext.ux.grid.HybridSummary\r
- * @extends Ext.ux.grid.GroupSummary\r
- * Adds capability to specify the summary data for the group via json as illustrated here:\r
- * <pre><code>\r
-{\r
-    data: [\r
-        {\r
-            projectId: 100,     project: 'House',\r
-            taskId:    112, description: 'Paint',\r
-            estimate:    6,        rate:     150,\r
-            due:'06/24/2007'\r
-        },\r
-        ...\r
-    ],\r
-\r
-    summaryData: {\r
-        'House': {\r
-            description: 14, estimate: 9,\r
-                   rate: 99, due: new Date(2009, 6, 29),\r
-                   cost: 999\r
-        }\r
-    }\r
-}\r
- * </code></pre>\r
- *\r
- */\r
-Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
-    /**\r
-     * @private\r
-     * @param {Object} rs\r
-     * @param {Object} cs\r
-     */\r
-    calculate : function(rs, cs){\r
-        var gcol = this.view.getGroupField(),\r
-            gvalue = rs[0].data[gcol],\r
-            gdata = this.getSummaryData(gvalue);\r
-        return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
-    },\r
-\r
-    /**\r
-     * <pre><code>\r
-grid.on('afteredit', function(){\r
-    var groupValue = 'Ext Forms: Field Anchoring';\r
-    summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
-    setTimeout(function(){ // simulate server call\r
-        // HybridSummary class implements updateSummaryData\r
-        summary.updateSummaryData(groupValue,\r
-            // create data object based on configured dataIndex\r
-            {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
-    }, 2000);\r
-});\r
-     * </code></pre>\r
-     * @param {String} groupValue\r
-     * @param {Object} data data object\r
-     * @param {Boolean} skipRefresh (Optional) Defaults to false\r
-     */\r
-    updateSummaryData : function(groupValue, data, skipRefresh){\r
-        var json = this.grid.getStore().reader.jsonData;\r
-        if(!json.summaryData){\r
-            json.summaryData = {};\r
-        }\r
-        json.summaryData[groupValue] = data;\r
-        if(!skipRefresh){\r
-            this.refreshSummary(groupValue);\r
-        }\r
-    },\r
-\r
-    /**\r
-     * Returns the summaryData for the specified groupValue or null.\r
-     * @param {String} groupValue\r
-     * @return {Object} summaryData\r
-     */\r
-    getSummaryData : function(groupValue){\r
-        var json = this.grid.getStore().reader.jsonData;\r
-        if(json && json.summaryData){\r
-            return json.summaryData[groupValue];\r
-        }\r
-        return null;\r
-    }\r
-});\r
-\r
-//backwards compat\r
-Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
+/*!
+ * Ext JS Library 3.2.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.GroupSummary
+ * @extends Ext.util.Observable
+ * A GridPanel plugin that enables dynamic column calculations and a dynamically
+ * updated grouped summary row.
+ */
+Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {
+    /**
+     * @cfg {Function} summaryRenderer Renderer example:<pre><code>
+summaryRenderer: function(v, params, data){
+    return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
+},
+     * </code></pre>
+     */
+    /**
+     * @cfg {String} summaryType (Optional) The type of
+     * calculation to be used for the column.  For options available see
+     * {@link #Calculations}.
+     */
+
+    constructor : function(config){
+        Ext.apply(this, config);
+        Ext.ux.grid.GroupSummary.superclass.constructor.call(this);
+    },
+    init : function(grid){
+        this.grid = grid;
+        var v = this.view = grid.getView();
+        v.doGroupEnd = this.doGroupEnd.createDelegate(this);
+
+        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
+        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
+        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
+        v.afterMethod('onUpdate', this.doUpdate, this);
+        v.afterMethod('onRemove', this.doRemove, this);
+
+        if(!this.rowTpl){
+            this.rowTpl = new Ext.Template(
+                '<div class="x-grid3-summary-row" style="{tstyle}">',
+                '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+                    '<tbody><tr>{cells}</tr></tbody>',
+                '</table></div>'
+            );
+            this.rowTpl.disableFormats = true;
+        }
+        this.rowTpl.compile();
+
+        if(!this.cellTpl){
+            this.cellTpl = new Ext.Template(
+                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
+                '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',
+                "</td>"
+            );
+            this.cellTpl.disableFormats = true;
+        }
+        this.cellTpl.compile();
+    },
+
+    /**
+     * Toggle the display of the summary row on/off
+     * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.
+     */
+    toggleSummaries : function(visible){
+        var el = this.grid.getGridEl();
+        if(el){
+            if(visible === undefined){
+                visible = el.hasClass('x-grid-hide-summary');
+            }
+            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');
+        }
+    },
+
+    renderSummary : function(o, cs){
+        cs = cs || this.view.getColumnData();
+        var cfg = this.grid.getColumnModel().config,
+            buf = [], c, p = {}, cf, last = cs.length-1;
+        for(var i = 0, len = cs.length; i < len; i++){
+            c = cs[i];
+            cf = cfg[i];
+            p.id = c.id;
+            p.style = c.style;
+            p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
+            if(cf.summaryType || cf.summaryRenderer){
+                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
+            }else{
+                p.value = '';
+            }
+            if(p.value == undefined || p.value === "") p.value = "&#160;";
+            buf[buf.length] = this.cellTpl.apply(p);
+        }
+
+        return this.rowTpl.apply({
+            tstyle: 'width:'+this.view.getTotalWidth()+';',
+            cells: buf.join('')
+        });
+    },
+
+    /**
+     * @private
+     * @param {Object} rs
+     * @param {Object} cs
+     */
+    calculate : function(rs, cs){
+        var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;
+        for(var j = 0, jlen = rs.length; j < jlen; j++){
+            r = rs[j];
+            for(var i = 0, len = cs.length; i < len; i++){
+                c = cs[i];
+                cf = cfg[i];
+                if(cf.summaryType){
+                    data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);
+                }
+            }
+        }
+        return data;
+    },
+
+    doGroupEnd : function(buf, g, cs, ds, colCount){
+        var data = this.calculate(g.rs, cs);
+        buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');
+    },
+
+    doWidth : function(col, w, tw){
+        if(!this.isGrouped()){
+            return;
+        }
+        var gs = this.view.getGroups(),
+            len = gs.length,
+            i = 0,
+            s;
+        for(; i < len; ++i){
+            s = gs[i].childNodes[2];
+            s.style.width = tw;
+            s.firstChild.style.width = tw;
+            s.firstChild.rows[0].childNodes[col].style.width = w;
+        }
+    },
+
+    doAllWidths : function(ws, tw){
+        if(!this.isGrouped()){
+            return;
+        }
+        var gs = this.view.getGroups(),
+            len = gs.length,
+            i = 0,
+            j, 
+            s, 
+            cells, 
+            wlen = ws.length;
+            
+        for(; i < len; i++){
+            s = gs[i].childNodes[2];
+            s.style.width = tw;
+            s.firstChild.style.width = tw;
+            cells = s.firstChild.rows[0].childNodes;
+            for(j = 0; j < wlen; j++){
+                cells[j].style.width = ws[j];
+            }
+        }
+    },
+
+    doHidden : function(col, hidden, tw){
+        if(!this.isGrouped()){
+            return;
+        }
+        var gs = this.view.getGroups(),
+            len = gs.length,
+            i = 0,
+            s, 
+            display = hidden ? 'none' : '';
+        for(; i < len; i++){
+            s = gs[i].childNodes[2];
+            s.style.width = tw;
+            s.firstChild.style.width = tw;
+            s.firstChild.rows[0].childNodes[col].style.display = display;
+        }
+    },
+    
+    isGrouped : function(){
+        return !Ext.isEmpty(this.grid.getStore().groupField);
+    },
+
+    // Note: requires that all (or the first) record in the
+    // group share the same group value. Returns false if the group
+    // could not be found.
+    refreshSummary : function(groupValue){
+        return this.refreshSummaryById(this.view.getGroupId(groupValue));
+    },
+
+    getSummaryNode : function(gid){
+        var g = Ext.fly(gid, '_gsummary');
+        if(g){
+            return g.down('.x-grid3-summary-row', true);
+        }
+        return null;
+    },
+
+    refreshSummaryById : function(gid){
+        var g = Ext.getDom(gid);
+        if(!g){
+            return false;
+        }
+        var rs = [];
+        this.grid.getStore().each(function(r){
+            if(r._groupId == gid){
+                rs[rs.length] = r;
+            }
+        });
+        var cs = this.view.getColumnData(),
+            data = this.calculate(rs, cs),
+            markup = this.renderSummary({data: data}, cs),
+            existing = this.getSummaryNode(gid);
+            
+        if(existing){
+            g.removeChild(existing);
+        }
+        Ext.DomHelper.append(g, markup);
+        return true;
+    },
+
+    doUpdate : function(ds, record){
+        this.refreshSummaryById(record._groupId);
+    },
+
+    doRemove : function(ds, record, index, isUpdate){
+        if(!isUpdate){
+            this.refreshSummaryById(record._groupId);
+        }
+    },
+
+    /**
+     * Show a message in the summary row.
+     * <pre><code>
+grid.on('afteredit', function(){
+    var groupValue = 'Ext Forms: Field Anchoring';
+    summary.showSummaryMsg(groupValue, 'Updating Summary...');
+});
+     * </code></pre>
+     * @param {String} groupValue
+     * @param {String} msg Text to use as innerHTML for the summary row.
+     */
+    showSummaryMsg : function(groupValue, msg){
+        var gid = this.view.getGroupId(groupValue),
+             node = this.getSummaryNode(gid);
+        if(node){
+            node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';
+        }
+    }
+});
+
+//backwards compat
+Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;
+
+
+/**
+ * Calculation types for summary row:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>
+ * </ul></div>
+ * <p>Custom calculations may be implemented.  An example of
+ * custom <code>summaryType=totalCost</code>:</p><pre><code>
+// define a custom summary function
+Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
+    return v + (record.data.estimate * record.data.rate);
+};
+ * </code></pre>
+ * @property Calculations
+ */
+
+Ext.ux.grid.GroupSummary.Calculations = {
+    'sum' : function(v, record, field){
+        return v + (record.data[field]||0);
+    },
+
+    'count' : function(v, record, field, data){
+        return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
+    },
+
+    'max' : function(v, record, field, data){
+        var v = record.data[field];
+        var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];
+        return v > max ? (data[field+'max'] = v) : max;
+    },
+
+    'min' : function(v, record, field, data){
+        var v = record.data[field];
+        var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];
+        return v < min ? (data[field+'min'] = v) : min;
+    },
+
+    'average' : function(v, record, field, data){
+        var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
+        var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));
+        return t === 0 ? 0 : t / c;
+    }
+};
+Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;
+
+/**
+ * @class Ext.ux.grid.HybridSummary
+ * @extends Ext.ux.grid.GroupSummary
+ * Adds capability to specify the summary data for the group via json as illustrated here:
+ * <pre><code>
+{
+    data: [
+        {
+            projectId: 100,     project: 'House',
+            taskId:    112, description: 'Paint',
+            estimate:    6,        rate:     150,
+            due:'06/24/2007'
+        },
+        ...
+    ],
+
+    summaryData: {
+        'House': {
+            description: 14, estimate: 9,
+                   rate: 99, due: new Date(2009, 6, 29),
+                   cost: 999
+        }
+    }
+}
+ * </code></pre>
+ *
+ */
+Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {
+    /**
+     * @private
+     * @param {Object} rs
+     * @param {Object} cs
+     */
+    calculate : function(rs, cs){
+        var gcol = this.view.getGroupField(),
+            gvalue = rs[0].data[gcol],
+            gdata = this.getSummaryData(gvalue);
+        return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);
+    },
+
+    /**
+     * <pre><code>
+grid.on('afteredit', function(){
+    var groupValue = 'Ext Forms: Field Anchoring';
+    summary.showSummaryMsg(groupValue, 'Updating Summary...');
+    setTimeout(function(){ // simulate server call
+        // HybridSummary class implements updateSummaryData
+        summary.updateSummaryData(groupValue,
+            // create data object based on configured dataIndex
+            {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
+    }, 2000);
+});
+     * </code></pre>
+     * @param {String} groupValue
+     * @param {Object} data data object
+     * @param {Boolean} skipRefresh (Optional) Defaults to false
+     */
+    updateSummaryData : function(groupValue, data, skipRefresh){
+        var json = this.grid.getStore().reader.jsonData;
+        if(!json.summaryData){
+            json.summaryData = {};
+        }
+        json.summaryData[groupValue] = data;
+        if(!skipRefresh){
+            this.refreshSummary(groupValue);
+        }
+    },
+
+    /**
+     * Returns the summaryData for the specified groupValue or null.
+     * @param {String} groupValue
+     * @return {Object} summaryData
+     */
+    getSummaryData : function(groupValue){
+        var reader = this.grid.getStore().reader,
+            json = reader.jsonData,
+            fields = reader.recordType.prototype.fields,
+            v;
+            
+        if(json && json.summaryData){
+            v = json.summaryData[groupValue];
+            if(v){
+                return reader.extractValues(v, fields.items, fields.length);
+            }
+        }
+        return null;
+    }
+});
+
+//backwards compat
+Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;