再Google,找到一个看似写的比较好的 http://www.cnblogs.com/over140/archive/2009/05/06/1449892.html

期间主要部分也是借鉴官方论坛上的东西,效果也很漂亮。

然后又看到一篇,提到了3个方法,并做了比较(http://www.cnblogs.com/over140/archive/2010/06/28/1766608.html

而且,把之前提到的那一个归结为“很明显是最复杂的,基本可以被淘汰。”

而我确有不同的看法,我恰恰认为这种“最复杂”的方法,有很多优点:

1. 这种sum计算,理应交给前台,减轻服务器压力。我用硬翻页,也不存在总合计的问题,后台也只能得到“当页合计”

2. 总体虽然复杂,但接口简洁,对业务画面代码干扰很小

3. 效果漂亮,合计始终保持在最下行显示,不与滚动条联动

4. 可以分别计算多列的合计

5. 除了“合计”,还可以计算 总数、最大值、最小值、平均值

于是,按照第一个URL中的方法开始,但始终有小Bug,下载了他的代码,倒是没有运行,但比看帖子里的片段,更清晰了。

最后,我找出了几处Bug和多余之处,改善了接口的友好度,降低了耦合度,总结分享一下:

1. 无需对Extjs的JsonReader.js做重载。真的不用override readRecords方法

2. 自行扩展的GridSummary类的onLayout方法中有一句

  1. this.scroller.setHeight(vh - this.summary.getHeight());

报错:没有this.summary 
   也确实是没有定义,官网论坛上的那个版本中,也没有这一句

但,这句代码,确实有意义,让滚动条的管辖范围高度让出地下的合计行。也似乎应该有一个“控件”在哪儿,取其高度,减之。但暂时没找到,临时用

  1. this.scroller.setHeight(vh - 30);

代替。

纠正:此处其实不存在这个Bug,是我一开始搞错了

3. 业务画面中实例化 JsonReader 时,参数

  1. dataSum: 'dataSum'

显得比较突兀。其实,在第一点中已经说了,不需要override JsonReader的readRecords方法,这个JsonReader 的属性,其实也是不需要的。

4. 渲染器里

  1. var renderSummary = function(o, cs, cm) {
  2. return '合计:'+jr.dataSum;
  3. }

这个jr,即刚才实例化的JsonReader,两者有了耦合,这一点不好。其实,这里函数的第一个参数,即o,其实已经是合计数量了。

进一步美化一下,自己写个Renderer,放在自己的工具类里就行了。

  1. summaryRenderer : function(format){
  2. return function(v){
  3. var val = Ext.util.Format.number(v, format);
  4. if(v >= 0){
  5. return '<span style="color:black;font-weight:bold;">' +  '本页合计 : ' + val + '</span>';
  6. }else{
  7. return '<span style="color:red;font-weight:bold;">' + '本页合计 : ' + val + '</span>';
  8. }
  9. };
  10. }

业务画面这样用:

  1. summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')

可以格式化(3位一个逗号),负数变红色,整体黑体显示

注:“本页合计:”总是在合计列中,感觉有2点不妥:1. 本身合计位数就大,这样很容易要求列宽要加大才能显示下;2. 如果两列以上显示合计,“本页合计:”出现多次似乎也怪怪的。 
    所以,我改进了一点点UI,具体请见下面补充部分

5. 原作者似乎忘记了,他在GridSummary类里还定义了类中类Ext.ux.grid.GridSummary.Calculations 
   这个是几个不同算法的实现,来实现上面所说的几个优点中的最后一个:除了“合计”,还可以计算 总数、最大值、最小值、平均值。

但,在业务画面中,我们需要在定义ColumnModel时,声明这一列要算 合计 还是 平均值...而原作者去忘了这个。(我没有运行他的代码,不知道那代码能不能有结果)

总结一下,经过我的改造。业务画面只需2处修改: 
1. 在定义ColumnModel时,增加2个属性即可:

  1. summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0'),
  2. summaryType: 'sum' // 这个不写的话,默认是'sum'

2. 在GridPanel实例化参数中增加一个plugin

  1. plugins: new Ext.ux.grid.GridSummary(),

其实,我们绝大部分时间都是用“合计”其他几个很少用,但又舍不得丢弃这些功能。那么就给GridSummary增加一个默认 summaryType: 'sum' 功能呗,原文calculate 方法中,把

  1. if (cf.summaryType) {

改成

  1. if (cf.summaryRenderer){
  2. if(!cf.summaryType) {
  3. f.summaryType = 'sum';  // default to Sum, you can define 'count', 'max', 'min', 'average' in the Column defination
  4. }

即可。

【最后在总结一下,放上整个代码】

业务画面只需

1. 在定义ColumnModel时,增加1个属性:

  1. summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')

如:

  1. },{
  2. hidden : false,
  3. header : '数量',
  4. dataIndex : 'qty',
  5. align: 'right',
  6. sortable : true,
  7. renderer: Ext.util.Format.numberRenderer('0,0'),
  8. summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')
  9. },{

2. 在GridPanel实例化参数中增加一个plugin

  1. return new Ext.grid.GridPanel({
  2. store: ds,
  3. columns: gridColums,
  4. plugins: new Ext.ux.grid.GridSummary()

另外增加一个GridSummary.js类

  1. Ext.ns('Ext.ux.grid');
  2. Ext.ux.grid.GridSummary = function(config) {
  3. Ext.apply(this, config);
  4. };
  5. Ext.extend(Ext.ux.grid.GridSummary, Ext.util.Observable, {
  6. init : function(grid) {
  7. this.grid = grid;
  8. this.cm = grid.getColumnModel();
  9. this.view = grid.getView();
  10. var v = this.view;
  11. // override GridView's onLayout() method
  12. v.onLayout = this.onLayout;
  13. v.afterMethod('render', this.refreshSummary, this);
  14. v.afterMethod('refresh', this.refreshSummary, this);
  15. v.afterMethod('syncScroll', this.syncSummaryScroll, this);
  16. v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
  17. v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
  18. v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
  19. // update summary row on store's add/remove/clear/update events
  20. grid.store.on({
  21. add: this.refreshSummary,
  22. remove: this.refreshSummary,
  23. clear: this.refreshSummary,
  24. update: this.refreshSummary,
  25. scope: this
  26. });
  27. if (!this.rowTpl) {
  28. this.rowTpl = new Ext.Template(
  29. '<div class="x-grid3-summary-row x-grid3-gridsummary-row-offset">',
  30. '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
  31. '<tbody><tr>{cells}</tr></tbody>',
  32. '</table>',
  33. '</div>'
  34. );
  35. this.rowTpl.disableFormats = true;
  36. }
  37. this.rowTpl.compile();
  38. if (!this.cellTpl) {
  39. this.cellTpl = new Ext.Template(
  40. '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
  41. '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
  42. "</td>"
  43. );
  44. this.cellTpl.disableFormats = true;
  45. }
  46. this.cellTpl.compile();
  47. },
  48. calculate : function(rs, cm) {
  49. var data = {}, cfg = cm.config;
  50. for (var i = 0, len = cfg.length; i < len; i++) { // loop through all columns in ColumnModel
  51. var cf = cfg[i], // get column's configuration
  52. cname = cf.dataIndex; // get column dataIndex
  53. // initialise grid summary row data for
  54. // the current column being worked on
  55. data[cname] = 0;
  56. if (cf.summaryRenderer){
  57. if(!cf.summaryType) {
  58. cf.summaryType = 'sum'; // default to Sum, you can define 'count', 'max', 'min', 'average' in the Column defination
  59. }
  60. for (var j = 0, jlen = rs.length; j < jlen; j++) {
  61. var r = rs[j]; // get a single Record
  62. data[cname] = Ext.ux.grid.GridSummary.Calculations[cf.summaryType](r.get(cname), r, cname, data, j);
  63. }
  64. }
  65. }
  66. return data;
  67. },
  68. onLayout : function(vw, vh) {
  69. if (Ext.type(vh) != 'number') { // handles grid's height:'auto' config
  70. return;
  71. }
  72. // note: this method is scoped to the GridView
  73. if (!this.grid.getGridEl().hasClass('x-grid-hide-gridsummary')) {
  74. // readjust gridview's height only if grid summary row is visible
  75. //this.scroller.setHeight(vh - this.summary.getHeight());
  76. this.scroller.setHeight(vh - 30);
  77. }
  78. },
  79. syncSummaryScroll : function() {
  80. var mb = this.view.scroller.dom;
  81. this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;
  82. this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
  83. },
  84. doWidth : function(col, w, tw) {
  85. var s = this.view.summary.dom;
  86. s.firstChild.style.width = tw;
  87. s.firstChild.rows[0].childNodes[col].style.width = w;
  88. },
  89. doAllWidths : function(ws, tw) {
  90. var s = this.view.summary.dom, wlen = ws.length;
  91. s.firstChild.style.width = tw;
  92. var cells = s.firstChild.rows[0].childNodes;
  93. for (var j = 0; j < wlen; j++) {
  94. cells[j].style.width = ws[j];
  95. }
  96. },
  97. doHidden : function(col, hidden, tw) {
  98. var s = this.view.summary.dom,
  99. display = hidden ? 'none' : '';
  100. s.firstChild.style.width = tw;
  101. s.firstChild.rows[0].childNodes[col].style.display = display;
  102. },
  103. renderSummary : function(o, cs, cm) {
  104. cs = cs || this.view.getColumnData();
  105. var cfg = cm.config,
  106. buf = [],
  107. last = cs.length - 1;
  108. for (var i = 0, len = cs.length; i < len; i++) {
  109. var c = cs[i], cf = cfg[i], p = {};
  110. p.id = c.id;
  111. p.style = c.style;
  112. p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
  113. if (cf.summaryType || cf.summaryRenderer) {
  114. p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
  115. } else {
  116. p.value = '';
  117. }
  118. //此处设置默认不显示时用什么符号标记
  119. if (p.value == undefined || p.value === "") p.value = "-";
  120. buf[buf.length] = this.cellTpl.apply(p);
  121. }
  122. return this.rowTpl.apply({
  123. tstyle: 'width:' + this.view.getTotalWidth() + ';',
  124. cells: buf.join('')
  125. });
  126. },
  127. refreshSummary : function() {
  128. var g = this.grid, ds = g.store,
  129. cs = this.view.getColumnData(),
  130. cm = this.cm,
  131. rs = ds.getRange(),
  132. data = this.calculate(rs, cm),
  133. buf = this.renderSummary({data: data}, cs, cm);
  134. if (!this.view.summaryWrap) {
  135. this.view.summaryWrap = Ext.DomHelper.insertAfter(this.view.scroller, {
  136. tag: 'div',
  137. cls: 'x-grid3-gridsummary-row-inner'
  138. }, true);
  139. }
  140. this.view.summary = this.view.summaryWrap.update(buf).first();
  141. },
  142. toggleSummary : function(visible) { // true to display summary row
  143. var el = this.grid.getGridEl();
  144. if (el) {
  145. if (visible === undefined) {
  146. visible = el.hasClass('x-grid-hide-gridsummary');
  147. }
  148. el[visible ? 'removeClass' : 'addClass']('x-grid-hide-gridsummary');
  149. this.view.layout(); // readjust gridview height
  150. }
  151. },
  152. getSummaryNode : function() {
  153. return this.view.summary
  154. }
  155. });
  156. Ext.reg('gridsummary', Ext.ux.grid.GridSummary);
  157. /*
  158. * all Calculation methods are called on each Record in the Store
  159. * with the following 5 parameters:
  160. *
  161. * v - cell value
  162. * record - reference to the current Record
  163. * colName - column name (i.e. the ColumnModel's dataIndex)
  164. * data - the cumulative data for the current column + summaryType up to the current Record
  165. * rowIdx - current row index
  166. */
  167. Ext.ux.grid.GridSummary.Calculations = {
  168. sum : function(v, record, colName, data, rowIdx) {
  169. return data[colName] + Ext.num(v, 0);
  170. },
  171. count : function(v, record, colName, data, rowIdx) {
  172. return rowIdx + 1;
  173. },
  174. max : function(v, record, colName, data, rowIdx) {
  175. return Math.max(Ext.num(v, 0), data[colName]);
  176. },
  177. min : function(v, record, colName, data, rowIdx) {
  178. return Math.min(Ext.num(v, 0), data[colName]);
  179. },
  180. average : function(v, record, colName, data, rowIdx) {
  181. var t = data[colName] + Ext.num(v, 0), count = record.store.getCount();
  182. return rowIdx == count - 1 ? (t / count) : t;
  183. }
  184. }

最后附上效果图

--------------------------------------------------------------------------------

【补充】

很快我就发现了横滚动条的Bug,网友 @lihao312 也看出来这个Bug了,并推荐给我官方的groupsummery。

我看了官方的groupsummery的例子,感觉2点:

1. 它很强大,不仅仅可以用来做合计,还有更强大的分组功能,只用它来做合计,有点大炮打蚊子了

2. 它要求调用方(业务画面)的代码与之的耦合度较高,为了使用它,还要引入几个类,并声明、实例化不少配置和实体

因此,我没有选择groupsummery,而是继续解决本文方法的横滚动条Bug

看代码,其实是有处理滚动条的代码的

  1. syncSummaryScroll : function() {
  2. var mb = this.view.scroller.dom;
  3. this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;
  4. this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
  5. },

看到了吧,不仅有,还写了两遍,原出处注释是因为仅执行一遍IE无效果,所以要执行两遍。

跟踪显示,即便执行2次,这个this.view.summaryWrap.dom.scrollLeft即便被赋值为某个数后,但查看其值仍然是0

经过一番研究后发现,原因是 this.view.summaryWrap.dom.scrollLeftMax == 0

正当我在为如何改变scrollLeftMax的值(这个也赋值后仍为0)而一筹莫展时,搜到了一个帖子(http://blog.sina.com.cn/s/blog_4e5922c901011gm5.html)

虽然这个帖子的内容和我之前参考的那个(本文第一个链接)一模一样,幸亏我耐心地看了整个页面,才发现在留言中 @guxin 网友给出的提示,实验一下果真见效。

我将其代码优化了一下: 
GridSummary.js 
1. 删除原有的syncSummaryScroll 方法,及其监听 
2. 在refreshSummary函数最后加上

  1. this.view.scroller.setStyle('overflow-x', 'hidden');
  2. var gridView= this.view;
  3. this.view.summary.setStyle('overflow-x', 'scroll');
  4. this.view.summary.on("scroll", function(){
  5. gridView.scroller.scrollTo('Left', gridView.summary.getScroll().left);
  6. }

至此,不仅横滚联动,而且横滚动条是在合计行下方的,UI效果更好

另一点更新,是上面红字提到的,把“本页合计:”放到第一列或指定列中显示,合计所在列仅显示合计的数值。

GridSummary.js的 renderSummary 方法 倒数第二行

  1. if (p.value == undefined || p.value === '') p.value = '-';

改成

  1. if (p.value == undefined || p.value === ''){
  2. //"本页合计:"这个title默认放置在第一列,也可以在配置项【summaryTitleColumn】中指定
  3. if((cf.dataIndex === this.summaryTitleColumn) || (!this.summaryTitleColumn && i === 1)){
  4. p.value = '本页合计:';
  5. }else{
  6. p.value = '';
  7. }
  8. }

业务画面实例化GridPanel时,plgins配置项的GridSummary构造函数可以指定title的显示列

  1. plugins: new Ext.ux.grid.GridSummary() // "本页合计:"固定在第一列
  2. //plugins: new Ext.ux.grid.GridSummary({summaryTitleColumn:'name'}) // "本页合计:"在'name'列(可跟着'name'列被拖拽位置)

这样一来,上文中提到的 MyExt.util.Renderer.summaryRenderer 也用不着了,换成

  1. summaryRenderer : Ext.util.Format.numberRenderer('0,0')

就行了

源码地址:http://dl2.iteye.com/upload/attachment/0106/2146/c93ad8ad-49f8-375d-9c82-83e5a1206140.rar

转自:http://tonylian.iteye.com/blog/1735525

给Extjs的GridPanel增加“合计”行(转)的更多相关文章

  1. easyui grid 增加合计行

    一.首先,easyui  grid 的 showfooter 属性设置为 true $aplgrid.datagrid({ data: globalExpenseClaimForm.ExpenseCl ...

  2. 分享一个带有合计行功能的DataGridView扩展

    因为一个Winform的项目中需要用到带有合计行的表格,并且需要满足以下需求: 合计行可自动对需要求和的列进行求和计算; 合计行必须固定(冻结)在表格的最底部,且其位置不受滚动条的滚动而移动; 可以设 ...

  3. FineUI大版本升级,外置ExtJS库、去AXD化、表格合计行、表格可编辑单元格的增删改、顶部菜单框架

    这是一篇很长的文章,在开始正文之前,请允许我代表目前排名前 20 中唯一的 .Net 开源软件 FineUI 拉下选票: 投票地址: https://code.csdn.net/2013OSSurve ...

  4. ExtJS如何取得GridPanel当前选择行数据对象 - nuccch的专栏 - 博客频道 - CSDN.NET

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...

  5. .net dataGridView当鼠标经过时当前行背景色变色;然后【给GridView增加单击行事件,并获取单击行的数据填充到页面中的控件中】

    1.首先在前台dataGridview属性中增加onRowDataBound属性事件 2.然后在后台Observing_RowDataBound事件中增加代码 protected void Obser ...

  6. NC nc5.x报表设置合计行是否显示

    首先要先继承UI类 /** * 设置合计行是否显示 */ public TotalsReportUI() { super(); getReportBase().getBodyPanel().setTo ...

  7. WPF 增加合计一栏

    占坑中  先抛个参考链接 http://stackoverflow.com/questions/678690/how-can-i-create-a-group-footer-in-a-wpf-list ...

  8. 对FineU框架Grid多表头合计行导出Excel的回顾

    年前用FineUI开发遇到了这样一个问题,Grid多表头合计行不能导出,后面到官方示例找了一下,庆幸的是找到了多表头的导出示例.然后当时为了省事,直接就复制粘贴完事,也没有仔细的研究代码.后来运行一看 ...

  9. 【前端】Vue和Vux开发WebApp日志四、增加命令行参数

    转载请注明出处:http://www.cnblogs.com/shamoyuu/p/vue_vux_4.html 项目github地址:https://github.com/shamoyuu/vue- ...

随机推荐

  1. gridview获取当前行索引的方法

    在用GridView控件时,我们经常会碰到获取当前行的索引,通过索引进行许多操作.例如,可以获得当前行某一个控件元素:设置某一元素的值等等. 下面结合实例介绍几种获得GridView当前行索引值的方法 ...

  2. c++网络通信(与服务器通信聊天)和c#网络通信

    c++网络通信(有待整理) 链接:http://pan.baidu.com/s/1i3nMLKT 密码:ksi8 c#网络通信(tcp/udp两部分) TCP发送端: using System; us ...

  3. LABJS源码浅析

    一.关于LABjs的简单介绍 作者:Kyle Simpson 作用:动态并行加载脚本文件 以及 管理加载脚本文件的执行顺序 官网:http://www.labjs.com/ 二.关于LABjs的使用 ...

  4. oracle存储参数(storage子句)含义及设置技巧

    可用于:表空间.回滚段.表.索引.分区.快照.快照日志 参数名称 缺省值 最小值 最大值 说明 INITIAL 5(数据块) 2(数据块) 操作系统限定 分配给Segment的第一个Extent的大小 ...

  5. HtmlParser基础教程

    1.相关资料 官方文档:http://htmlparser.sourceforge.net/samples.html API:http://htmlparser.sourceforge.net/jav ...

  6. AngularJS心得体会

    AngularJS早些时候有过了解,知道这是一个JS的MVC框架,同类型的框架还有Backbone等.这次是由于项目需要,学习了两天的Angular后开始着手改之前的项目代码,这里大概说一下这一周学习 ...

  7. leetcode 237 Delete Node in a Linked List python

    题目: Write a function to delete a node (except the tail) in a singly linked list, given only access t ...

  8. How to delete the icons of Win7 desktop shortcuts

    1. Copy the following bat code in txt type file, 2. save it as file extension type bat, run it as ad ...

  9. 以Qemu模拟Linux,学习Linux内核

    文章名称:以Qemu模拟Linux,学习Linux内核作      者:five_cent文章地址:http://www.cnblogs.com/senix/archive/2013/02/21/29 ...

  10. C#中HashTable的用法 【转】

    一,哈希表(Hashtable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似keyvalue的键值对,其中 ...