easyui的异步树创建很简单,只需要指定一个获取树的JSON数据的URL地址就可以了,API是这样写的:
异步树后台代码设计方式有很多种,我说下我的设计。
先来解释下每个字段的含义:
id:节点ID,用于后台接收查询对应数据
pid:父节点ID,用于获取指定ID节点的子节点
text:节点名称
iconCls:节点的图标
state:节点的状态,有open和closed俩种状态,数据库设置默认值为open,与easyui此属性默认值保持一致
checked:节点是否被选中,此属性需要打开tree的checkbox属性,默认值为-1,表示无此属性,可以设置0,表示不选中,1表示选中
attributes:是否具有自定义属性,数据库设置默认值为0表示无,如果有的话需要设置成1
children:是否具有子节点,数据库设置默认值为0表示无,如果有的话需要设置成1
loadChildren:是否立即加载子节点,数据库设置默认值为0表示不立即加载,需要的话可以设置成1表示立即加载子节点
layer:层数,表示该节点位于树的哪一层,根节点层数为1,兄弟节点层数相同,子节点层数+1
tid:节点ID
name:属性名
value:属性值
state:状态,表示此属性是否生效,默认值为1表示有效,可以设置为0表示无效
先来说说这么设计表会遇到的几种情况:
1、某节点state值为open,children值为0,loadChildren值为0
此时该节点在页面显示为一个文件的形态
2、某节点state值为open,children值为0,loadChildren值为1
这种情况虽然立即加载但是并没有打开children,因此后面我们会在业务逻辑中判断为第一种情况,因此同样显示为一个文件的形态
3、某节点state只为open,children值为1,loadChildren值为0
这种情况表示节点为打开状态且有子节点,那么就应该无视立即加载这个属性,效果同第4条
4、某节点state只为open,children值为1,loadChildren值为1
这种情况表示该节点即存在子节点也立即加载,在后面业务逻辑中会根据当前的节点ID去获取子节点数据,如果子节点中的某个子节点为同样属性,会继续递归获取该子节点下的所有子节点数据,以此类推。此时节点显示为文件夹形态,子节点根据自身设置显示各自的形态
5、某节点state只为closed,children值为0,loadChildren值为0
这样会出现如图所示情况:
因为当展开节点后却无子节点数据,这种情况我们要在后面业务逻辑中阻止,阻止方式为不设置state属性值,转为JSON时不设置state属性(easyui会默认为open)
6、某节点state只为closed,children值为0,loadChildren值为1
已经设置了不存在子节点立即加载也应该无效,情况和第5条一样,同样在业务逻辑中阻止这种情况,
7、某节点state只为closed,children值为1,loadChildren值为0
正常应该存在的情况,此时该节点在页面上显示为一个文件夹形态,该节点具备子节点但是不是立即加载,根据API描述,当展开一个closed的节点,且该节点的此节点属性并没有加载,也就是该节点的JSON数据中无children属性,会将此节点的ID通过URL发送至后台去获取子节点数据。
8、某节点state只为closed,children值为1,loadChildren值为1
这也是正常应该存在的情况,此时该节点在页面上显示为一个未展开的文件夹形态,但是此时该节点下的子节点(或者子子节点,根据子节点属性按需求递归,以此类推)已经加载完毕,这时候展开该节点,会显示已经加载好的子节点,因此并不会向后台发送请求。
根据数据库设计及上面分析的情况,可以看出1、2都作为情况1处理,3、4为一种情况,5、6为一种情况,7为一种情况,8为一种情况。
public class TreeNode {
/**节点ID*/
private Integer id;
/**父节点树ID*/
private Integer pid;
/**节点名称*/
private String text;
/**节点的图标*/
private String iconCls;
/**状态*/
private String state;
/**是否选中:-1-无此属性 0-否 1-是*/
private Integer checked;
/**是否有属性:0-无 1-有*/
private Integer attributes;
/**是否有子节点:0-无 1-有*/
private Integer children;
/**是否立即加载子节点:0-不加载 1-加载*/
private Integer loadChildren;
/**所在层数 根节点为层1、兄弟节点层数相同、子节点层数+1*/
private Integer layer;
/**子节点集合*/
private List treeNodes;
/**节点属性集合*/
private List treeNodeAttrs;
/**将当前节点及其子节点转为JSON格式*/
public String toJson() {
return "[" + toJson(this, "") + "]";
}
/**将当前节点下的子节点转为JSON格式*/
public String childToJson() {
String json = "[";
for(int i = 1; i <= treeNodes.size(); i++) {
json += toJson(treeNodes.get(i - 1), "");
if(i != treeNodes.size()) {
json += ",";
}
}
json += "]";
return json;
}
private String toJson(TreeNode treeNode, String json) {
json = "{\n\"id\": "+ treeNode.getId();
json += ",\n\"text\": \""+ treeNode.getText() +"\"";
if(treeNode.getIconCls() != null) {
json += ",\n\"iconCls\": \""+ treeNode.getIconCls() +"\"";
}
if(treeNode.getChecked() == 0) {
json += ",\n\"checked\": "+ false;
}else if(treeNode.getChecked() == 1) {
json += ",\n\"checked\": "+ true;
}
//如果该节点具备自定义属性且存在自定义属性数据
if(treeNode.getAttributes() == 1 && treeNode.getTreeNodeAttrs() != null) {
json += ",\n\"attributes\":{\n";
List attrsList = treeNode.getTreeNodeAttrs();
for(int i = 1; i <= attrsList.size(); i++) {
json += "\""+ attrsList.get(i - 1).getName() +"\": \""+ attrsList.get(i - 1).getValue() +"\"";
if(i != attrsList.size()) {
json += ",\n";
}else {
json += "\n";
}
}
json += "}";
}
if("open".equals(treeNode.getState())) {
json += ",\n\"state\": \"open\"";
}else if("closed".equals(treeNode.getState()) && treeNode.getChildren() == 1){
//设置closed属性必须保证有子节点,否则页面文件夹打开会显示根目录
json += ",\n\"state\": \"closed\"";
}
//俩种情况可以拼接子节点JSON字符串
//1-设置立即加载子节点且存在子节点且子节点属性不为空
//2-当状态不是closed时(不设置时easyui默认open),无视是否设置立即加载,只要保证存在子节点且子节点属性不为空
if((treeNode.getLoadChildren() == 1 && treeNode.getChildren() == 1
&& treeNode.getTreeNodes() != null) || (!"closed".equals(treeNode.getState())
&& treeNode.getChildren() == 1 && treeNode.getTreeNodes() != null)) {
json += ",\n\"children\":[";
for(int i = 1; i <= treeNode.getTreeNodes().size(); i++) {
json += toJson(treeNode.getTreeNodes().get(i - 1), json);
if(i != treeNode.getTreeNodes().size()) {
json += ",";
}
}
json += "]";
}
json += "\n}";
return json;
}
//get set 构造方法略
public class TreeNodeAttr {
private Integer tid;
private String name;
private String value;
private Integer state;
//get set 构造方法略
}
这里我用的Spring+SpringMVC+MyBatis
@Service
public interface TreeMapper {
/**查询指定的节点*/
public TreeNode selectTreeNodeInfo(TreeNode treeNode);
/**查询指定节点的属性*/
public TreeNode selectTreeNodeAttrs(TreeNode treeNode);
/**查询指定节点下的子节点*/
public TreeNode selectChildrenTreeNodesInfo(TreeNode treeNode);
}
"com.bc.dao.tree.TreeMapper">
id="treeNodeAttrs" type="TreeNode">
property="treeNodeAttrs" ofType="TreeNodeAttr">
<result property="name" column="name"/>
<result property="value" column="value"/>
id="treeNodeChildrens" type="TreeNode">
property="treeNodes" ofType="TreeNode">
<id property="id" column="id"/>
<result property="pid" column="pid"/>
<result property="text" column="text"/>
<result property="iconCls" column="iconCls"/>
<result property="state" column="state"/>
<result property="checked" column="checked"/>
<result property="attributes" column="attributes"/>
<result property="children" column="children"/>
<result property="loadChildren" column="loadChildren"/>
<result property="layer" column="layer"/>
public interface TreeService {
/**获取根据ID获取节点的信息*/
public TreeNode searchTreeNodeInfo(Integer id);
}
@Service
public class TreeServiceImpl implements TreeService {
@Resource
private TreeMapper treeMapper;
@Override
public TreeNode searchTreeNodeInfo(Integer id) {
//创建一个节点对象
TreeNode treeNode = new TreeNode();
//设置要查询的ID
treeNode.setId(id);
//查询该节点信息
treeNode = treeMapper.selectTreeNodeInfo(treeNode);
//如果该节点有属性
if(treeNode.getAttributes() == 1) {
//查询该节点的属性后存入该节点
treeNode.setTreeNodeAttrs(treeMapper.selectTreeNodeAttrs(treeNode)
.getTreeNodeAttrs());
}
//如果该节点有子节点
//这里只需要判断一个属性就可以了,因为这个节点是我们手动打开的,
//也就是打开前就能保证其余属性都符合要求
if(treeNode.getChildren() == 1) {
treeNode.setTreeNodes(getTreeNodeChildrens(treeMapper
.selectChildrenTreeNodesInfo(treeNode).getTreeNodes()));
}
return treeNode;
}
/**
* 获取节点集合中每个子节点、子节点中的子节点...以此类推
* @param treeNodes
* @return
*/
private List getTreeNodeChildrens(List treeNodes) {
//如果该节点有属性
if(t.getAttributes() == 1) {
//查询该节点的属性后存入该节点
t.setTreeNodeAttrs(treeMapper.selectTreeNodeAttrs(t)
.getTreeNodeAttrs());
}
for(TreeNode t : treeNodes) {
/**
* 这里是程序递归自动判断是否获取子节点,因此需要根据数据库设计那里的8种情况来过滤条件
*
* 1、state:open,children:0,loadChildren:0
* 这种情况不加载子节点
* 2、state:open,children:0,loadChildren:1
* 这种情况不加载子节点
* 3、state:open,children:1,loadChildren:0
* 这种情况加载(open状态无视立即加载)
* 4、state:open,children:1,loadChildren:1
* 这种情况加载
* 5、state:closed,children:0,loadChildren:0
* 这种情况不加载子节点
* 6、state:closed,children:0,loadChildren:1
* 这种情况不加载子节点
* 7、state:closed,children:1,loadChildren:0
* 这种情况不加载子节点
* 8、state:closed,children:1,loadChildren:1
* 这种情况加载
*
* 因此只有3、4和8俩种情况可以加载,其他全部拦截
* 因为easyui对于不设置的state默认为open,因此使用"!closed"来代替"open"
*/
if((t.getLoadChildren() == 1 && t.getChildren() == 1) ||
(!"closed".equals(t.getState()) && t.getChildren() == 1)) {
t.setTreeNodes(getTreeNodeChildrens(treeMapper.selectChildrenTreeNodesInfo(t)
.getTreeNodes()));
}
}
return treeNodes;
}
}
@RequestMapping(value = "/data",method = RequestMethod.POST)
public void root(HttpServletResponse response, Integer id) throws Exception {
TreeNode tree = null;
String json = "";
if(id == null) {
//第一次进入页面ID为空,此时查询出根节点数据返回
tree = treeService.searchTreeNodeInfo(1);
//此时需要将根节点及其子节点(条件符合)转为JSON
json = tree.toJson();
}else{
//再次访问此属性是展开closed状态的节点,发送ID来查询子节点
tree = treeService.searchTreeNodeInfo(id);
//此时只需要将子节点转为JSON
json = tree.childToJson();
}
response.getWriter().print(json);
}
其中”/data”就是easyui的tree属性的URL地址,页面代码略。
最后我们往数据库表中插入一些数据试试
对于自定义属性表就不做测试了
代码没认真检查过,不知道有无可优化的地方及错误,仅供参考。
下一篇讲解使用easyui的属性、事件和方法实现一些效果
链接地址:待编辑