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