嗯,先上图
然后是代码,这个么,这个框架只能给出JS代码,后台代码考虑到公司版权问题不敢乱放,只能描述下了
JS代码就一行:
var maintree = new AutomationTree({ name: 'navigator', rootVisible: false });
指定创建一个名称为navigator的AutomationTree(名字俗了些,谁有好名字给个建议?)
重点在AutomationTree的封装上了,其实也很简单,甚至就两个方法100行代码:


1 function AutomationTree(config) { 2 this.name = config.name; 3 if (config.border == null) { 4 config.border = false; 5 } 6 if (config.width == null) { 7 config.width = '100%'; 8 } 9 if (config.height == null) { 10 config.height = '100%'; 11 } 12 var requestconfig = { 13 url: '/Tree/Get', 14 method: 'post', 15 params: { name: this.name }, 16 async: false, 17 callback: function(options, success, response) { 18 var r = Ext.util.JSON.decode(response.responseText); 19 var rootconfig = { text: unescape(r.label), expanded: true }; 20 if (r.iconCls != null) { 21 rootconfig.iconCls = r.iconCls; 22 } 23 if (r.icon != null) { 24 rootconfig.icon = r.icon; 25 } 26 if (r.handler != null) { 27 rootconfig.listeners = { 'click': function() { eval(this.handler); } }; 28 } 29 var root = new Ext.tree.TreeNode(rootconfig); 30 root.handler = unescape(r.handler); 31 root.dataRefresh = r.expandRefresh == true; 32 root.dataRead = false; 33 root.nodeID = "-1"; 34 root.dataID = ""; 35 config.root = root; 36 } 37 }; 38 Ext.Ajax.request(requestconfig); 39 40 AutomationTree.superclass.constructor.call(this, config); 41 this.addListener('beforeexpandnode', function(node) { this.loadsub(node); }); 42 }; 43 44 Ext.extend(AutomationTree, Ext.tree.TreePanel, { 45 loadsub: function(node) { 46 if (node.dataRefresh == true || node.dataRead == false) { 47 if (node.dataRead && node.hasChildNodes()) { 48 while (node.hasChildNodes()) { 49 node.removeChild(node.lastChild); 50 } 51 } 52 node.dataRead = true; 53 var requestconfig = { 54 url: '/Tree/GetChild', 55 method: 'post', 56 params: { name: this.name, id: node.nodeID, dataID: node.dataID }, 57 async: false, 58 callback: function(options, success, response) { 59 var list = Ext.util.JSON.decode(response.responseText); 60 for (var i = 0; i < list.length; i++) { 61 var r = list[i]; 62 var nodeconfig = { text: unescape(r.label) }; 63 if (r.iconCls != null) { 64 nodeconfig.iconCls = r.iconCls; 65 } 66 if (r.icon != null) { 67 nodeconfig.icon = r.icon; 68 } 69 if (r.handler != null) { 70 nodeconfig.listeners = { 'click': function() { eval(this.handler); } }; 71 } 72 if (r.hasChild == true) { 73 nodeconfig.expandable = true; 74 } 75 76 var childnode = new Ext.tree.TreeNode(nodeconfig); 77 childnode.handler = unescape(r.handler); 78 childnode.nodeID = r.id; 79 childnode.dataRead = false; 80 if (r.dataID != null) { 81 childnode.dataID = r.dataID; 82 } 83 else { 84 childnode.dataID = ""; 85 } 86 if (r.autoExpand == true) { 87 childnode.expanded = true; 88 } 89 childnode.dataRefresh = r.expandRefresh == true; 90 node.appendChild(childnode); 91 } 92 } 93 }; 94 Ext.Ajax.request(requestconfig); 95 } 96 } 97 });
这个树就两个方法:根据名称创建根,根据上一层节点创建下一层节点
对应的Ajax请求为TreeController里的两个方法,这个更简单,加一起70行代码


public string Get(string name) { return AutomationTreeConfig.Instance().GetTree(name).ToJson(); } public string GetChild(string name, string id, string dataID) { AutomationTree tree = AutomationTreeConfig.Instance().GetTree(name); AutomationTreeNode pnode = tree.GetNode(id); List<string> list = tree.GetSubNode(id); if (list == null || list.Count == 0) { return "[]"; } string r = ""; foreach (string nodeid in list) { AutomationTreeNode node = tree.GetNode(nodeid); INodePrivilegeFilter filter = null; if (!string.IsNullOrEmpty(node.PrivilgeFilter)) { filter = IOCContainer.GetIOCObject(node.PrivilgeFilter) as INodePrivilegeFilter; if (filter.TypeCheck) { if (!filter.CheckBeforeLoad(name, pnode, node, dataID)) { continue; } } } string loader = string.IsNullOrEmpty(node.Loader) ? "defaultnodeloader" : node.Loader; INodeLoader nodeloader = IOCContainer.GetIOCObject(loader) as INodeLoader; List<TreeData> datalist = nodeloader.Load(name, pnode, node, dataID); List<INodeRender> renderlist = new List<INodeRender>(); if (!string.IsNullOrEmpty(node.Renders)) { string[] s = node.Renders.Split(','); foreach (string render in s) { renderlist.Add(IOCContainer.GetIOCObject(render) as INodeRender); } } if (datalist != null && datalist.Count > 0) { if (!string.IsNullOrEmpty(node.PrivilgeFilter) && !filter.TypeCheck) { datalist = filter.Check(name, pnode, node, dataID, datalist); } foreach (TreeData data in datalist) { r += r == "" ? "[" : ","; if (!string.IsNullOrEmpty(node.Renders)) { foreach (INodeRender noderender in renderlist) { noderender.Render(data); } } r += data.ToJson(); } } } r += "]"; return r; }
代码就不解释了,后台的更多类也不解释了,只解释下架构:
系统通过配置,设定了树结构,前台提供一个名称,创建树的根
然后自动通过根创建第一层树叶,下面每一层根据需要再去创建
创建每一层时,通过IOC容器(这个是自己写的)指定的Loader的名字去获取一个Loader的IOC对象,这是个INodeLoader接口,通过这个接口加载数据
然后如果这个树叶上有指定的Render对象,则加载Render的IOC对象(如果是多个,按顺序运行),通过INodeDataRender对象去对树叶的信息进行处理(例如一些格式化、数据转换等等)
还有第三个IOC对象:INodePrivilegeFilter接口,通过这个接口去控制权限或业务逻辑过滤,使当前数据不返回给用户,过滤有两种,一是过滤整个定义的数据类型,二是在返回的数据集里进行数据过滤,通过TypeCheck去判定
这三个IOC对象,就可以完全实现整个树的控制,如果有业务逻辑很特殊,则只需要再写个特殊的IOC对象(一般通过继承重载来实现)就满足要求了
树的配置定义:


<?xml version="1.0" encoding="utf-8" ?> <ApplicationConfig> <tree name="navigator" moniker="navigator" label="系统导航" iconCls="icon-system" > <node id="01" moniker="systemmodule" iconCls="icon-system" label="系统管理" autoExpand="true" expandRefresh="true"> <node id="0101" moniker="usermanage" iconCls="icon-user" label="用户管理" handler="showPanel(userpanel);" privilegeFilter="nodeprivilegefilter" params="{datamoniker:'systemmodule',dataid:'1002',privid:0}"/> <node id="0102" moniker="rolemanage" iconCls="icon-role" label="角色管理" handler="showPanel(rolepanel);" privilegeFilter="nodeprivilegefilter" params="{datamoniker:'systemmodule',dataid:'1003',privid:0}"/> <node id="0103" moniker="modulemanage" iconCls="icon-module" label="系统模块管理" handler="showPanel(modulepanel);" privilegeFilter="nodeprivilegefilter" params="{datamoniker:'systemmodule',dataid:'1001',privid:0}"/> <node id="0104" moniker="flowmanage" iconCls="icon-flow" label="流程管理" handler="showPanel(flowpanel);" privilegeFilter="nodeprivilegefilter" params="{datamoniker:'systemmodule',dataid:'1004',privid:0}"/> </node> <node id="02" moniker="mail" label="我的邮箱" iconCls="icon-marketing" handler="openmail();" /> <node id="03" moniker="requestlist" label="新增申请" iconCls="icon-request" > <node id="0301" moniker="carrequest" label="申请用车" iconCls="icon-car" handler="window_add_carrequest.show();" /> </node> <node id="04" moniker="myapprove" label="等待我审批项目" iconCls="icon-wait" expandRefresh="true"> <node id="0401" moniker="approveitem" label="等待审批" iconCls="icon-wait" loader="approveitemloader"/> </node> </tree> </ApplicationConfig>
这颗树支持各种类型子节点,默认的导航树上当然大多都是写死的(这种情况使用默认的Loader加载),通过特殊的Loader就可以实现类似BOM的结构
如果是递归的结构是这么写的


<node id="05" loader="parentloader"......> <node id="0501" loader="childloader" -------> <node id="0501" /> </node> </node>
xml属性是这样的:
id:用于标识父子结构,如果有相同ID node出现(例如不同的类型下面都挂了相同的子结构),后面的无需再定义属性和子结构
moniker:数据类型标识(例如用户为User,客户为Customer),如果是非数据类型,可以任意定义
iconCls和icon:icon的css或图片路径,只能出现一个
label:显示的字符串,如果是实体类型可以在label里定义显示参数(写成{field}及其他格式等等)
handler:点击后执行的代码,可以为空(这也是我的框架里核心的设计之一:脚本完全进行Command模式封装)
autoExpand:当载入此节点后自动加载并展开下级节点数据
expandRefresh:每次点击展开此节点时自动重新加载数据
loader:加载数据的IOC对象,写死的节点为空(使用的是defaultnodeloader),加载数据的对象默认为“defaultdataloader”
privilgeFilter:节点是否显示的权限类IOC对象,为空不控制权限
renders:对数据进行特殊处理的IOC对象,如果有多个,用逗号隔开
params:一个Json字符串,给loader、privilegeFilter和renders对象使用的额外的参数,可以有多个参数,IOC对象各自挑选自己需要的
这颗树的封装基本上把复杂的ExtJS树编程给变成了配置,写起来简单多了,尤其是脚本,几乎没有代码了,后台代码也很清晰,易于调试,不象接手维护的过去的几个项目,脚本满天飞,Ajax代码一大堆,痛苦的要死(我实在是读不懂那些一层一层跳来跳去的脚本啊)