`
TonyLian
  • 浏览: 396996 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

给Extjs的GridPanel增加“合计”行

阅读更多
奇怪,也算玩了2年Ext,怎么还没有一篇关于Ext的博文?这一篇算是第一篇了。

要给Extjs的GridPanel增加“合计”行,有这个想法的童鞋想必不在少数。

我首先看了官网的例子,没有“合计”。

再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方法中有一句
  
this.scroller.setHeight(vh - this.summary.getHeight());

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

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

代替。

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

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

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

4. 渲染器里
var renderSummary = function(o, cs, cm) {
        return '合计:'+jr.dataSum;
    }

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

    进一步美化一下,自己写个Renderer,放在自己的工具类里就行了。
		summaryRenderer : function(format){
	        return function(v){
	            var val = Ext.util.Format.number(v, format);
	            if(v >= 0){
	                return '<span style="color:black;font-weight:bold;">' +  '本页合计 : ' + val + '</span>';
	            }else{
	                return '<span style="color:red;font-weight:bold;">' + '本页合计 : ' + val + '</span>';
	            }
	        };
	    }


    业务画面这样用:
summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')

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

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


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

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

    总结一下,经过我的改造。业务画面只需2处修改:
1. 在定义ColumnModel时,增加2个属性即可:
	summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0'),
	summaryType: 'sum' // 这个不写的话,默认是'sum'


2. 在GridPanel实例化参数中增加一个plugin
plugins: new Ext.ux.grid.GridSummary(),


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

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

    即可。



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

业务画面只需

1. 在定义ColumnModel时,增加1个属性:
	summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')


如:
	},{
		hidden : false,
		header : '数量',
		dataIndex : 'qty',
		align: 'right',
		sortable : true,
		renderer: Ext.util.Format.numberRenderer('0,0'), 
		summaryRenderer: MyExt.util.Renderer.summaryRenderer('0,0')
	},{


2. 在GridPanel实例化参数中增加一个plugin
return new Ext.grid.GridPanel({
	store: ds,
	columns: gridColums,
	plugins: new Ext.ux.grid.GridSummary()


另外增加一个GridSummary.js类
Ext.ns('Ext.ux.grid');

Ext.ux.grid.GridSummary = function(config) {
        Ext.apply(this, config);
};

Ext.extend(Ext.ux.grid.GridSummary, Ext.util.Observable, {
    init : function(grid) {
        this.grid = grid;
        this.cm = grid.getColumnModel();
        this.view = grid.getView();

        var v = this.view;

        // override GridView's onLayout() method
        v.onLayout = this.onLayout;

        v.afterMethod('render', this.refreshSummary, this);
        v.afterMethod('refresh', this.refreshSummary, this);
        v.afterMethod('syncScroll', this.syncSummaryScroll, this);
        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);

        // update summary row on store's add/remove/clear/update events
        grid.store.on({
            add: this.refreshSummary,
            remove: this.refreshSummary,
            clear: this.refreshSummary,
            update: this.refreshSummary,
            scope: this
        });

        if (!this.rowTpl) {
            this.rowTpl = new Ext.Template(
                '<div class="x-grid3-summary-row x-grid3-gridsummary-row-offset">',
                    '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
                        '<tbody><tr>{cells}</tr></tbody>',
                    '</table>',
                '</div>'
            );
            this.rowTpl.disableFormats = true;
        }
        this.rowTpl.compile();

        if (!this.cellTpl) {
            this.cellTpl = new Ext.Template(
                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
                    '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
                "</td>"
            );
            this.cellTpl.disableFormats = true;
        }
        this.cellTpl.compile();
    },

    calculate : function(rs, cm) {
        var data = {}, cfg = cm.config;
        for (var i = 0, len = cfg.length; i < len; i++) { // loop through all columns in ColumnModel
            var cf = cfg[i], // get column's configuration
                cname = cf.dataIndex; // get column dataIndex

            // initialise grid summary row data for
            // the current column being worked on
            data[cname] = 0;
			
            if (cf.summaryRenderer){
            	if(!cf.summaryType) {
	            	cf.summaryType = 'sum';	// default to Sum, you can define 'count', 'max', 'min', 'average' in the Column defination 
	            }
	            for (var j = 0, jlen = rs.length; j < jlen; j++) {
	                var r = rs[j]; // get a single Record
	                data[cname] = Ext.ux.grid.GridSummary.Calculations[cf.summaryType](r.get(cname), r, cname, data, j);
	            }
            }            
        }

        return data;
    },

    onLayout : function(vw, vh) {
        if (Ext.type(vh) != 'number') { // handles grid's height:'auto' config
            return;
        }
        // note: this method is scoped to the GridView
        if (!this.grid.getGridEl().hasClass('x-grid-hide-gridsummary')) {
            // readjust gridview's height only if grid summary row is visible
            //this.scroller.setHeight(vh - this.summary.getHeight());
        	this.scroller.setHeight(vh - 30);
        }
    },

    syncSummaryScroll : function() {
        var mb = this.view.scroller.dom;

        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;
        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
    },

    doWidth : function(col, w, tw) {
        var s = this.view.summary.dom;

        s.firstChild.style.width = tw;
        s.firstChild.rows[0].childNodes[col].style.width = w;
    },

    doAllWidths : function(ws, tw) {
        var s = this.view.summary.dom, wlen = ws.length;

        s.firstChild.style.width = tw;

        var cells = s.firstChild.rows[0].childNodes;

        for (var j = 0; j < wlen; j++) {
            cells[j].style.width = ws[j];
        }
    },

    doHidden : function(col, hidden, tw) {
        var s = this.view.summary.dom,
            display = hidden ? 'none' : '';

        s.firstChild.style.width = tw;
        s.firstChild.rows[0].childNodes[col].style.display = display;
    },

    renderSummary : function(o, cs, cm) {
        cs = cs || this.view.getColumnData();
        var cfg = cm.config,
            buf = [],
            last = cs.length - 1;

        for (var i = 0, len = cs.length; i < len; i++) {
            var c = cs[i], cf = cfg[i], p = {};

            p.id = c.id;
            p.style = c.style;
            p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');

            if (cf.summaryType || cf.summaryRenderer) {
                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
            } else {
                p.value = '';
            }
            //此处设置默认不显示时用什么符号标记
            if (p.value == undefined || p.value === "") p.value = "-";
            buf[buf.length] = this.cellTpl.apply(p);
        }

        return this.rowTpl.apply({
            tstyle: 'width:' + this.view.getTotalWidth() + ';',
            cells: buf.join('')
        });
    },

    refreshSummary : function() {
        var g = this.grid, ds = g.store,
            cs = this.view.getColumnData(),
            cm = this.cm,
            rs = ds.getRange(),
            data = this.calculate(rs, cm),
            buf = this.renderSummary({data: data}, cs, cm);

        if (!this.view.summaryWrap) {
            this.view.summaryWrap = Ext.DomHelper.insertAfter(this.view.scroller, {
                tag: 'div',
                cls: 'x-grid3-gridsummary-row-inner'
            }, true);
        }
        this.view.summary = this.view.summaryWrap.update(buf).first();
    },

    toggleSummary : function(visible) { // true to display summary row
        var el = this.grid.getGridEl();

        if (el) {
            if (visible === undefined) {
                visible = el.hasClass('x-grid-hide-gridsummary');
            }
            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-gridsummary');

            this.view.layout(); // readjust gridview height
        }
    },

    getSummaryNode : function() {
        return this.view.summary
    }
});
Ext.reg('gridsummary', Ext.ux.grid.GridSummary);

/*
 * all Calculation methods are called on each Record in the Store
 * with the following 5 parameters:
 *
 * v - cell value
 * record - reference to the current Record
 * colName - column name (i.e. the ColumnModel's dataIndex)
 * data - the cumulative data for the current column + summaryType up to the current Record
 * rowIdx - current row index
 */
Ext.ux.grid.GridSummary.Calculations = {
    sum : function(v, record, colName, data, rowIdx) {
        return data[colName] + Ext.num(v, 0);
    },

    count : function(v, record, colName, data, rowIdx) {
        return rowIdx + 1;
    },

    max : function(v, record, colName, data, rowIdx) {
        return Math.max(Ext.num(v, 0), data[colName]);
    },

    min : function(v, record, colName, data, rowIdx) {
        return Math.min(Ext.num(v, 0), data[colName]);
    },

    average : function(v, record, colName, data, rowIdx) {
        var t = data[colName] + Ext.num(v, 0), count = record.store.getCount();
        return rowIdx == count - 1 ? (t / count) : t;
    }
}


最后附上效果图




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


【补充】

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

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

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

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

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

看代码,其实是有处理滚动条的代码的
 syncSummaryScroll : function() {  
        var mb = this.view.scroller.dom;  
  
        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;  
        this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)  
    }, 


看到了吧,不仅有,还写了两遍,原出处注释是因为仅执行一遍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函数最后加上
        this.view.scroller.setStyle('overflow-x', 'hidden');
        var gridView= this.view;
        this.view.summary.setStyle('overflow-x', 'scroll');
        this.view.summary.on("scroll", function(){
        	gridView.scroller.scrollTo('Left', gridView.summary.getScroll().left);
		});
        }

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


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

GridSummary.js的 renderSummary 方法 倒数第二行
if (p.value == undefined || p.value === '') p.value = '-';


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


业务画面实例化GridPanel时,plgins配置项的GridSummary构造函数可以指定title的显示列
	plugins: new Ext.ux.grid.GridSummary() // "本页合计:"固定在第一列
	//plugins: new Ext.ux.grid.GridSummary({summaryTitleColumn:'name'}) // "本页合计:"在'name'列(可跟着'name'列被拖拽位置)


这样一来,上文中提到的 MyExt.util.Renderer.summaryRenderer 也用不着了,换成
summaryRenderer : Ext.util.Format.numberRenderer('0,0')

就行了





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

2015年2月补充:我也破一次例,提供一个成品的下载(注:代码适用于Ext3.x)
(我的原则一般是授之以渔,而不是授之以鱼的,这次破个例)
  • 大小: 229.8 KB
  • 大小: 196 KB
5
1
分享到:
评论
12 楼 c253898303 2018-01-08  
求和的时候说是调用store的基础方法,这个能重写吗?如果可以重写那个方法?
11 楼 TonyLian 2015-02-28  
wangjiabin 写道
我看15年2月份还有最新回复, 忍不住问一下, 劳烦高手指导. 按照上面的说明从头到位修改后, chrome可以用, ie下没有滚动条了(ie8和ie11都试过了). summary.setStyle('overflow-x','scroll')那里没起作用. 百思不得其解, 希望能够指点一下, 谢谢!


一直都是没有兼容性问题的,IE8+,Chrome,FF都没问题的。
这样吧,我把整个代码贴个附件上来,你到帖子最下方找下载吧。
10 楼 wangjiabin 2015-02-27  
我看15年2月份还有最新回复, 忍不住问一下, 劳烦高手指导. 按照上面的说明从头到位修改后, chrome可以用, ie下没有滚动条了(ie8和ie11都试过了). summary.setStyle('overflow-x','scroll')那里没起作用. 百思不得其解, 希望能够指点一下, 谢谢!
9 楼 TonyLian 2015-02-05  
yepingping 写道
发现使用max 和 min 这来获取对应的值有错误。
 


谢谢你这个提示。确实平时主要在用sum,没有使用到min和max,所以没有发现这个问题。
8 楼 yepingping 2014-06-15  
yepingping 写道
发现使用max 和 min 这来获取对应的值有错误。
在 calculate  方法初始设置了  data[cname] = 0;
假设一列值是  20,30,10,5
所以最小值都是0

假设一列值是  -20,-30,-10,-5
所以最大值都是0


解决方法
if (cf.summaryRenderer){
            if(!cf.summaryType) {
            cf.summaryType = 'sum'; // default to Sum, you can define 'count', 'max', 'min', 'average' in the Column defination
            }
            // initialise grid summary row data for
// the current column being worked on
if(cf.summaryType=='min'){
data[cname] =Number.MAX_VALUE;
}else if(cf.summaryType=='max'){
data[cname] = Number.MIN_VALUE;
}else{
data[cname] = 0;
}
            for (var j = 0, jlen = rs.length; j < jlen; j++) {
                var r = rs[j]; // get a single Record
                data[cname] = Ext.ux.grid.GridSummary.Calculations[cf.summaryType](r.get(cname), r, cname, data, j);
            }
            } 
7 楼 yepingping 2014-06-15  
发现使用max 和 min 这来获取对应的值有错误。
在 calculate  方法初始设置了  data[cname] = 0;
假设一列值是  20,30,10,5
所以最小值都是0

假设一列值是  -20,-30,-10,-5
所以最大值都是0
6 楼 caishaolang 2014-02-26  
在谷歌浏览器测试可以,在IE不行,找了两天,发现少了一个CSS文件
http://extjs.com/forum/showthread.php?p=166417#post166417
在这个压缩包里有个CSS文件 引用后 完美解决问题
不过refreshSummary方法不过那个改动,改动过后IE8测试无法正常显示滚动条
5 楼 caishaolang 2014-02-22  
你好,我用了你的代码后,之前合计没有跟着滚动,后来看了你红色部分的修改,跟着修改后,发现 连滚动条都消失了,可以麻烦你把修改后的GridSummary.js文件发送给我吗(466120367@qq.com),谢谢
4 楼 TonyLian 2013-01-10  
li_qingxue 写道
合计跟着滚动的问题解决了吗,我现在照着你的写,滚动条在合计下面不好使,不知道什么原因,郁闷。。。


解决了,请看红色【补充】以下的内容
3 楼 li_qingxue 2013-01-04  
合计跟着滚动的问题解决了吗,我现在照着你的写,滚动条在合计下面不好使,不知道什么原因,郁闷。。。
2 楼 TonyLian 2012-11-27  
lihao312 写道
你那个合计能跟着滚动吗 不然如果下面的合计数据显示不完整,如果合计能跟着横着滚动就可以了。
其实extjs在3+以后就出groupsummery了
你可以在plugin里面看到的


我也发现横滚的Bug了,明显已经写了横滚的代码,但是对DOM的私有元素赋值没有起效果。
我还在研究,也去看看你说的groupsummery
1 楼 lihao312 2012-11-26  
你那个合计能跟着滚动吗 不然如果下面的合计数据显示不完整,如果合计能跟着横着滚动就可以了。
其实extjs在3+以后就出groupsummery了
你可以在plugin里面看到的

相关推荐

Global site tag (gtag.js) - Google Analytics