Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / examples / ux / GroupSummary.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /*!
8  * Ext JS Library 3.2.0
9  * Copyright(c) 2006-2010 Ext JS, Inc.
10  * licensing@extjs.com
11  * http://www.extjs.com/license
12  */
13 Ext.ns('Ext.ux.grid');
14
15 /**
16  * @class Ext.ux.grid.GroupSummary
17  * @extends Ext.util.Observable
18  * A GridPanel plugin that enables dynamic column calculations and a dynamically
19  * updated grouped summary row.
20  */
21 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {
22     /**
23      * @cfg {Function} summaryRenderer Renderer example:<pre><code>
24 summaryRenderer: function(v, params, data){
25     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
26 },
27      * </code></pre>
28      */
29     /**
30      * @cfg {String} summaryType (Optional) The type of
31      * calculation to be used for the column.  For options available see
32      * {@link #Calculations}.
33      */
34
35     constructor : function(config){
36         Ext.apply(this, config);
37         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);
38     },
39     init : function(grid){
40         this.grid = grid;
41         var v = this.view = grid.getView();
42         v.doGroupEnd = this.doGroupEnd.createDelegate(this);
43
44         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
45         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
46         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
47         v.afterMethod('onUpdate', this.doUpdate, this);
48         v.afterMethod('onRemove', this.doRemove, this);
49
50         if(!this.rowTpl){
51             this.rowTpl = new Ext.Template(
52                 '<div class="x-grid3-summary-row" style="{tstyle}">',
53                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
54                     '<tbody><tr>{cells}</tr></tbody>',
55                 '</table></div>'
56             );
57             this.rowTpl.disableFormats = true;
58         }
59         this.rowTpl.compile();
60
61         if(!this.cellTpl){
62             this.cellTpl = new Ext.Template(
63                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
64                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',
65                 "</td>"
66             );
67             this.cellTpl.disableFormats = true;
68         }
69         this.cellTpl.compile();
70     },
71
72     /**
73      * Toggle the display of the summary row on/off
74      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.
75      */
76     toggleSummaries : function(visible){
77         var el = this.grid.getGridEl();
78         if(el){
79             if(visible === undefined){
80                 visible = el.hasClass('x-grid-hide-summary');
81             }
82             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');
83         }
84     },
85
86     renderSummary : function(o, cs){
87         cs = cs || this.view.getColumnData();
88         var cfg = this.grid.getColumnModel().config,
89             buf = [], c, p = {}, cf, last = cs.length-1;
90         for(var i = 0, len = cs.length; i < len; i++){
91             c = cs[i];
92             cf = cfg[i];
93             p.id = c.id;
94             p.style = c.style;
95             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
96             if(cf.summaryType || cf.summaryRenderer){
97                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
98             }else{
99                 p.value = '';
100             }
101             if(p.value == undefined || p.value === "") p.value = "&#160;";
102             buf[buf.length] = this.cellTpl.apply(p);
103         }
104
105         return this.rowTpl.apply({
106             tstyle: 'width:'+this.view.getTotalWidth()+';',
107             cells: buf.join('')
108         });
109     },
110
111     /**
112      * @private
113      * @param {Object} rs
114      * @param {Object} cs
115      */
116     calculate : function(rs, cs){
117         var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;
118         for(var j = 0, jlen = rs.length; j < jlen; j++){
119             r = rs[j];
120             for(var i = 0, len = cs.length; i < len; i++){
121                 c = cs[i];
122                 cf = cfg[i];
123                 if(cf.summaryType){
124                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);
125                 }
126             }
127         }
128         return data;
129     },
130
131     doGroupEnd : function(buf, g, cs, ds, colCount){
132         var data = this.calculate(g.rs, cs);
133         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');
134     },
135
136     doWidth : function(col, w, tw){
137         if(!this.isGrouped()){
138             return;
139         }
140         var gs = this.view.getGroups(),
141             len = gs.length,
142             i = 0,
143             s;
144         for(; i < len; ++i){
145             s = gs[i].childNodes[2];
146             s.style.width = tw;
147             s.firstChild.style.width = tw;
148             s.firstChild.rows[0].childNodes[col].style.width = w;
149         }
150     },
151
152     doAllWidths : function(ws, tw){
153         if(!this.isGrouped()){
154             return;
155         }
156         var gs = this.view.getGroups(),
157             len = gs.length,
158             i = 0,
159             j, 
160             s, 
161             cells, 
162             wlen = ws.length;
163             
164         for(; i < len; i++){
165             s = gs[i].childNodes[2];
166             s.style.width = tw;
167             s.firstChild.style.width = tw;
168             cells = s.firstChild.rows[0].childNodes;
169             for(j = 0; j < wlen; j++){
170                 cells[j].style.width = ws[j];
171             }
172         }
173     },
174
175     doHidden : function(col, hidden, tw){
176         if(!this.isGrouped()){
177             return;
178         }
179         var gs = this.view.getGroups(),
180             len = gs.length,
181             i = 0,
182             s, 
183             display = hidden ? 'none' : '';
184         for(; i < len; i++){
185             s = gs[i].childNodes[2];
186             s.style.width = tw;
187             s.firstChild.style.width = tw;
188             s.firstChild.rows[0].childNodes[col].style.display = display;
189         }
190     },
191     
192     isGrouped : function(){
193         return !Ext.isEmpty(this.grid.getStore().groupField);
194     },
195
196     // Note: requires that all (or the first) record in the
197     // group share the same group value. Returns false if the group
198     // could not be found.
199     refreshSummary : function(groupValue){
200         return this.refreshSummaryById(this.view.getGroupId(groupValue));
201     },
202
203     getSummaryNode : function(gid){
204         var g = Ext.fly(gid, '_gsummary');
205         if(g){
206             return g.down('.x-grid3-summary-row', true);
207         }
208         return null;
209     },
210
211     refreshSummaryById : function(gid){
212         var g = Ext.getDom(gid);
213         if(!g){
214             return false;
215         }
216         var rs = [];
217         this.grid.getStore().each(function(r){
218             if(r._groupId == gid){
219                 rs[rs.length] = r;
220             }
221         });
222         var cs = this.view.getColumnData(),
223             data = this.calculate(rs, cs),
224             markup = this.renderSummary({data: data}, cs),
225             existing = this.getSummaryNode(gid);
226             
227         if(existing){
228             g.removeChild(existing);
229         }
230         Ext.DomHelper.append(g, markup);
231         return true;
232     },
233
234     doUpdate : function(ds, record){
235         this.refreshSummaryById(record._groupId);
236     },
237
238     doRemove : function(ds, record, index, isUpdate){
239         if(!isUpdate){
240             this.refreshSummaryById(record._groupId);
241         }
242     },
243
244     /**
245      * Show a message in the summary row.
246      * <pre><code>
247 grid.on('afteredit', function(){
248     var groupValue = 'Ext Forms: Field Anchoring';
249     summary.showSummaryMsg(groupValue, 'Updating Summary...');
250 });
251      * </code></pre>
252      * @param {String} groupValue
253      * @param {String} msg Text to use as innerHTML for the summary row.
254      */
255     showSummaryMsg : function(groupValue, msg){
256         var gid = this.view.getGroupId(groupValue),
257              node = this.getSummaryNode(gid);
258         if(node){
259             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';
260         }
261     }
262 });
263
264 //backwards compat
265 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;
266
267
268 /**
269  * Calculation types for summary row:</p><div class="mdetail-params"><ul>
270  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>
271  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>
272  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>
273  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>
274  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>
275  * </ul></div>
276  * <p>Custom calculations may be implemented.  An example of
277  * custom <code>summaryType=totalCost</code>:</p><pre><code>
278 // define a custom summary function
279 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
280     return v + (record.data.estimate * record.data.rate);
281 };
282  * </code></pre>
283  * @property Calculations
284  */
285
286 Ext.ux.grid.GroupSummary.Calculations = {
287     'sum' : function(v, record, field){
288         return v + (record.data[field]||0);
289     },
290
291     'count' : function(v, record, field, data){
292         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
293     },
294
295     'max' : function(v, record, field, data){
296         var v = record.data[field];
297         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];
298         return v > max ? (data[field+'max'] = v) : max;
299     },
300
301     'min' : function(v, record, field, data){
302         var v = record.data[field];
303         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];
304         return v < min ? (data[field+'min'] = v) : min;
305     },
306
307     'average' : function(v, record, field, data){
308         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
309         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));
310         return t === 0 ? 0 : t / c;
311     }
312 };
313 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;
314
315 /**
316  * @class Ext.ux.grid.HybridSummary
317  * @extends Ext.ux.grid.GroupSummary
318  * Adds capability to specify the summary data for the group via json as illustrated here:
319  * <pre><code>
320 {
321     data: [
322         {
323             projectId: 100,     project: 'House',
324             taskId:    112, description: 'Paint',
325             estimate:    6,        rate:     150,
326             due:'06/24/2007'
327         },
328         ...
329     ],
330
331     summaryData: {
332         'House': {
333             description: 14, estimate: 9,
334                    rate: 99, due: new Date(2009, 6, 29),
335                    cost: 999
336         }
337     }
338 }
339  * </code></pre>
340  *
341  */
342 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {
343     /**
344      * @private
345      * @param {Object} rs
346      * @param {Object} cs
347      */
348     calculate : function(rs, cs){
349         var gcol = this.view.getGroupField(),
350             gvalue = rs[0].data[gcol],
351             gdata = this.getSummaryData(gvalue);
352         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);
353     },
354
355     /**
356      * <pre><code>
357 grid.on('afteredit', function(){
358     var groupValue = 'Ext Forms: Field Anchoring';
359     summary.showSummaryMsg(groupValue, 'Updating Summary...');
360     setTimeout(function(){ // simulate server call
361         // HybridSummary class implements updateSummaryData
362         summary.updateSummaryData(groupValue,
363             // create data object based on configured dataIndex
364             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
365     }, 2000);
366 });
367      * </code></pre>
368      * @param {String} groupValue
369      * @param {Object} data data object
370      * @param {Boolean} skipRefresh (Optional) Defaults to false
371      */
372     updateSummaryData : function(groupValue, data, skipRefresh){
373         var json = this.grid.getStore().reader.jsonData;
374         if(!json.summaryData){
375             json.summaryData = {};
376         }
377         json.summaryData[groupValue] = data;
378         if(!skipRefresh){
379             this.refreshSummary(groupValue);
380         }
381     },
382
383     /**
384      * Returns the summaryData for the specified groupValue or null.
385      * @param {String} groupValue
386      * @return {Object} summaryData
387      */
388     getSummaryData : function(groupValue){
389         var reader = this.grid.getStore().reader,
390             json = reader.jsonData,
391             fields = reader.recordType.prototype.fields,
392             v;
393             
394         if(json && json.summaryData){
395             v = json.summaryData[groupValue];
396             if(v){
397                 return reader.extractValues(v, fields.items, fields.length);
398             }
399         }
400         return null;
401     }
402 });
403
404 //backwards compat
405 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;