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