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