奇怪,也算玩了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 时,参数
显得比较突兀。其实,在第一点中已经说了,不需要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 '' + '本页合计 : ' + val + '';
- }else{
- return '' + '本页合计 : ' + val + '';
- }
- };
- }
业务画面这样用:
- 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'
2. 在GridPanel实例化参数中增加一个plugin
- plugins: new Ext.ux.grid.GridSummary(),
其实,我们绝大部分时间都是用“合计”其他几个很少用,但又舍不得丢弃这些功能。那么就给GridSummary增加一个默认 summaryType: 'sum' 功能呗,原文calculate 方法中,把
改成
- if (cf.summaryRenderer){
- if(!cf.summaryType) {
- f.summaryType = 'sum';
- }
即可。
【最后在总结一下,放上整个代码】
业务画面只需
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;
-
-
- 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);
-
-
- 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(
- ''
- );
- this.rowTpl.disableFormats = true;
- }
- this.rowTpl.compile();
-
- if (!this.cellTpl) {
- this.cellTpl = new Ext.Template(
- '
',
- '
{value} ',
- "
| "
- );
- 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++) {
- var cf = cfg[i],
- cname = cf.dataIndex;
-
-
-
- data[cname] = 0;
-
- if (cf.summaryRenderer){
- if(!cf.summaryType) {
- cf.summaryType = 'sum';
- }
- for (var j = 0, jlen = rs.length; j < jlen; j++) {
- var r = rs[j];
- 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') {
- return;
- }
-
- if (!this.grid.getGridEl().hasClass('x-grid-hide-gridsummary')) {
-
-
- 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;
- },
-
- 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) {
- 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();
- }
- },
-
- getSummaryNode : function() {
- return this.view.summary
- }
- });
- Ext.reg('gridsummary', Ext.ux.grid.GridSummary);
-
-
-
-
-
-
-
-
-
-
-
- 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;
- }
- }
最后附上效果图