欢迎转载,但请保留原帖地址(http://www.liyonghome.cn/index.php/archives/187.html ) 及作者信息(深海区:[email protected])
选择这个控件,是因为在昨天进行的一项开发中,需要一个可编辑的GridPanel控件,另外由于科目一项分为3-4级的树形结构,因此就需要 找到一个合适的TreeGridPanel。
在网上查找很久,才发现了在Extjs3.0中,有Ext.ux.maximgb.tg.EditorGridPanel这样一个类提供了对我 需求的支持,初步分析一下,依然有这样一些问题。目前本人还在使用这个控件进行开发,如果有什么新的内容会及时更新,欢迎大家通过E-mail和我共同学 习。至于我的大多数修改都是通过使用来实现,并未修改源代码,其实诸如禁止指定行编辑,非叶子节点进行统计等,都可以通过修改源代码进行封装实现,但是由 于下周就要结束项目开发,时间紧迫,因此没有时间来完善。等项目间歇期有时间了,本人会考虑封装一个新的TreeGrid类来实现以下方法,希望能够和大 家讨论。
1.参数_lft和_rgt不知道如何设置,可不可以不用?
其实_lft和_rgt可以不用设置,网上很多代码提供如下样子的实现
var data = [ {"_id":1,"_parent":null,"_level":1,"_lft":1,"_rgt":98,"_is_leaf":false,"item":" 主营业务成本","YN":64.72}, {"_id":2,"_parent":1,"_level":2,"_lft":2,"_rgt":49,"_is_leaf":true,"item":" 网络维护成本","YN":64.72} ]; var record = Ext.data.Record.create([ {name: '_id', type: 'int'}, {name: '_level', type: 'int'}, {name: '_lft', type: 'int'}, {name: '_rgt', type: 'int'}, {name: '_is_leaf', type: 'bool'}, {name: 'item'}, {name: 'benbu', type: 'float'} ]); var store = new Ext.ux.maximgb.tg.NestedSetStore({ autoLoad : true, reader: new Ext.data.JsonReader({id: ‘_id’}, record), proxy: new Ext.data.MemoryProxy(data) });
在这里为什么需要设置_lft和_rgt,是因为Ext.ux.maximgb.tg.NestedSetStore的需要,可以通过 TreeGrid.js文件的源代码可以分析,在此Store中,树形结构使用的是基于左右值的无限分类算法。但是在我们的使用当中,去为此json对象 生成此参数却相当的麻烦,至少自己一下没能找到解决的办法。在研究这段的代码的同时,自己也发现了一个同样继承于 Ext.ux.maximgb.tg.AbstractTreeStore的 Ext.ux.maximgb.tg.AdjacencyListStore,最关键的是,这个类所需要的参数,是我们常见的_parent参数,也就是 基于父子关系的树形结构算法。
经过自己测试,可以只需要_parent就可以使用了,如下。
var data = [ {"_id":1,"_parent":null,"_level":1,"_is_leaf":false,"item":"主营业务成本","YN":64.72}, {"_id":2,"_parent":1,"_level":2,"_is_leaf":true,"item":"网络维护成本","YN":64.72} ]; var record = Ext.data.Record.create([ {name: '_id', type: 'int'}, {name: '_level', type: 'int'}, {name: '_is_leaf', type: 'bool'}, {name: 'item'}, {name: 'benbu', type: 'float'} ]); var store = new Ext.ux.maximgb.tg.AdjacencyListStore({ autoLoad : true, reader: new Ext.data.JsonReader({id: ‘_id’}, record), proxy: new Ext.data.MemoryProxy(data) });
在这里面就没有了_lft和_rgt参数,仅仅更换了一下Store而已。
2.树形结构能不能够默认展开?
通常情况下,大家习惯于在代码的最后调用一个展开方法即可。在源代码中,查到AdjacencyListStore含有一个 expandAll()方法,按照字面意思应该就是展开所有节点的方法。但是在使用当中,却发现没有效果。在expandAll()方法中有这样一个定 义:records = this.data.getRange(),records 应该就是此store中的所有数据的集合,得到以后分别遍历进行展开操作,在调试js的时候发现,得到的这个records.length居然等于0,思 考一会猜测应该是expandAll()比Store的load()方法优先了,在进行展开操作的时候还并未加载到数据。因此自己放弃了Store中的 autoLoad : true这项配置。手工写了load操作和expandAll操作。
store.load();
store.expandAll();
这样调整以后就可以默认展开了,但是当自己将测试的本地数据换成远程的动态调用时,发现又没有效果了,原因是store.load()是异步操作,由 于远程请求较慢,在store.load()操作还并未返回的时候就调用了store.expandAll()操作,这样也就和原来的错误原因一样了。
后来自己将store.expandAll()操作放在了store.load()操作的加载完毕事件里面,才满足了自己的需求,至于是手工的 store.load(),还是autoLoad : true,都无所谓了。
store.on(”load”,function(e){ store.expandAll(); });
3.如何自定义的禁止编辑指定的某一行?
在代码中,我们可以很容易的让某一列不可编辑,因为EditorGridPanel的模型是基于列模型的,在定义列的时候,我们增加了一个 editor属性,去赋值一个编辑器对象,我们不给其赋值就可以实现某一列不可编辑的功能,但是行呢?在api中,似乎没有提供这个功能,看来只有自己去 实现了。首先还是说说我自己的需求:
通过此图来描述我的需求。我的树形结构可能出现3-4级两种,图片所示是3级的情况,我允许编辑的行必须是树中叶子节点的行,比如“大修理”和 “维保”,而非叶子节点及根节点的数据,通过统计所在列的子节点对应数据得到。比如“网络维护成本-本部”,就是由“大修理-本部”和“维保-本部”累加 得到,此处的值不允许编辑。所以得出以下结论:叶子节点所在行可编辑,非叶子节点行不可编辑。(至于统计相关代码在后面讨论,此处仅讨论编辑问题)
因此,我们可以由以下代码实现:
grid.on(”beforeedit”,function(e){ var currRecord = e.record; if(!currRecord.get(’_is_leaf’)){ e.cancel = true; } });
此处是监听grid的beforeedit事件,在edit之前,判断此节点是不是叶子节点,如果不是叶子节点,则取消编辑状态。至于判断是不是叶子 节点,可以通过之前的_is_leaf进行判断(_is_leaf是必须设置的,会影响到树形结构前面的“+”符号图案),另外也可以通过_level进 行判断。
4.如何统计汇总所在列子节点的数值?
在这里,有两种方式进行统计,一种是后台统计好数据,通过json传递给前台Extjs,这种方式不再我们讨论范围,因为这种方式不需要讨论 ^_^。我们需要讨论的是,节点没有数据,仅叶子节点含有数据,节点数据是在前台汇总得到的值。
这里对所在列进行统计汇总,首先我就想到了ColumnModel的renderer(渲染)属性,此属性可以配置一个方法,在显示 EditorGridPanel时,调用渲染的方法来实现修改单元格的文字颜色、内容等功能。在这里我也将采用这种方式进行汇总数据。
我给ColumnModel的需要统计的列增加一个renderer:treeNodeSum属性,即在显示此列单元格时,每个单元格会分别调用 treeNodeSum方法。于是我们可以这样实现:
var totalValue; function treeNodeSum(value, cellmeta, record, rowIndex, columnIndex, store){ if(record.get(’_is_leaf’)){ return value; }else{ totalValue = 0; totalNode(record,cellmeta.id); return totalValue; } } function totalNode(record,fieldName){ if(record.get(’_is_leaf’)){ totalValue += record.get(fieldName); } var mixedCollection = store.query(”_parent”,record.get(”_id”)); mixedCollection.each(function(item,index,length){ totalNode(item,fieldName); }); }
首先是调用treeNodeSum方法,其中判断if(record.get(’_is_leaf’)),当此处为true时,证明此节点是叶 子节点,是被统计项,不需要进行统计,所以直接返回。而非叶子节点,均是统计项,在这里,我写了一个递归函数,去遍历子节点及子节点的字节点等,当子节点 是叶子节点时,将数值进行累加并返回。由此我们就可以在展示的时候汇总当前数据,并显示给用户了,也如之前的图所示,“主营业务成本”和“网络业务成本” 的数字均由前台统计出来的。
到这里,新的问题出来了,页面展示的时候,节点的统计数据出来了,但是当我修改后又怎么更新统计数据呢?将在下一个问题中进行讨论。
5.如何实时更新统计数据?
这里说到,在修改后实时统计,当然马上就联想到在编辑后这个事件下功夫了。首先找到编辑后的事件为:afteredit,
监听这个事件,当发生这个事件时去更新父节点的数据,并递归调用到根节点才结束循环。代码如下:
grid.on(”afteredit”,function(e){ var currValue; var parentRecord; var columnName = grid.getColumnModel().getDataIndex(e.column); var currRecord = e.record; while(currRecord.get(’_parent’)!==null){ parentRecord = store.getAt(store.find(”_id”,currRecord.get(’_parent’))); currValue = parentRecord.get(columnName); currValue -= e.originalValue; currValue += e.value; parentRecord.set(columnName,currValue); parentRecord.dirty = false; parentRecord.commit(); currRecord = parentRecord; } });
这样就可以在修改叶子节点数据后,实时的更新父节点的统计数据。但是这里出现一个情况,当代码去更新父节点的时候,父节点出现了带三角符号的已编辑状 态,在获取store的已修改数据时,会获取到这些数据,这是我们不希望的,因此最好能去除掉统计节点的编辑状态,避免获取修改数据时获取到。
6.如何取消已编辑状态并除去提示的三角符号?
其实在上一节的代码中已经给出了代码,这里进行一下说明,通常步骤分为以下三步:
parentRecord.set(columnName,currValue);
parentRecord.dirty = false;
parentRecord.commit();
首先是修改数据,这个时候页面上会出现三角形的符号提示此字段已被修改。这个时候我们需要设置dirty=false,这样在store获取修改数据 时就不会获得了,但是此刻界面上面没有更新,依然有三角符号的提示,这个时候我们需要commit()一下,就可以消除掉三角符号了。
7.如何一次性批量提交修改了的记录?
var results={}; var recordArray = store.getModifiedRecords(); var recordOld; for(var i=0;i items = recordArray[i].getChanges(); recordOld = recordArray[i].modified; for(var key in items){ if( recordOld[key]===”"){ results["item("+key+"#"+recordArray[i].get(”_id”)+”#Insert)”]=items[key]; }else{ results["item("+key+"#"+recordArray[i].get(”_id”)+”#Update)”]=items[key]; } } }
在这段代码里面,我们首先获取了已修改了的Record记录数组,之后再遍历这个数组,分别获得每一个修改了的具体字段,再通过判断原始值是不是 为”"(在这里突然想到,如果没有值的话,.modified会不会是null?等有时间了测试一下),当为空代表是新增的数据,否则是修改的数据。通过 这种方式,我们最终得到的resules对象,可以作为一个json对象通过Ajax的params属性直接赋值传递。
resules对象的json字符串例如:[{"item(YN#8888#Insert)":999}, {"item(YN#9999#Update)":888}]。
由于本人是使用java struts进行编程,因此后台可以用名叫item的Map进行获取(如何获取不明白的可以搜索下Map-Backed ActionForm的实现方式)。获得的Map的键值分别为:
YN#8888#Insert(键):999(值)
YN#9999#Update(键)::888(值)
这样的话我们就可以分析键来实现具体的业务了。
8.如何实时的分别提交修改了的记录?
此功能并未在我的代码里面使用,主要考虑到在用户的编辑过程当中一直不停的操作数据库不太好,但是如果数据较少的朋友也可以尝试此方式。可以通 过注册afteredit事件实现。
grid.on(”afteredit”,function(e){
});
在此方法中传入的e参数实际上是一个Event,包含了此数据的值信息,行列值等,通过这些数据就可以得到此数据的归属,之后发起一个异步的Ajax 请求。这里最好还是做一下失败判断,如果有保存失败的提示用户,并且将修改了的值还原最好。当修改成功以后,清除修改提示状态。