GroupSummary.js 12 KB

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