为了让ligerui能更好的工作,我们需要对ligerui的一些常用组件进行封装。首先我们就对最基本的菜单组件进行封装。下面我们先来观察下ligerui中树组件。
从上面的图片中,我们可以清楚的看到,这就是一棵树。树是一种常用的数据结构,下面引用百度百科的语句:
树的递归定义:
树(Tree)是n(n≥0)个结点的有限集T,T为空时称为空树,否则它满足如下两个条件:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)其余的结点可分为m(m≥0)个互不相交的子集Tl,T2,…,Tm,其中每个子集本身又是一棵树,并称其为根的子树(Subree)
下面我们使用接口来描述一颗树。在java中,接口可以很好的描述现实世界中的某种物体具备的行为特点,所以接口非常适合。下面是具体代码:
package net.itaem.ligerui; import java.util.List; /** * liger ui中菜单模型 * 这个接口主要用来描述菜单的一些相关操作 * @date 2014-08-19 10:17 am * @author 骆宏 * @email [email protected] * * */ public interface ITreeModel { /** * 定义一个字符串,用来表示树模型中的菜单节点 * */ String MENU = "menu"; /** * 定义一个字符串,用来表示数模型中的叶子节点 * */ String LEAF = "leaf"; /** * 返回当前菜单的结构层次 * @return 返回菜单的结构层次 * 如果是叶子节点,那么返回0 * */ public int level(); /** * 返回当前节点的所有子节点,子节点包括了子菜单以及叶子节点 * @return 返回当前菜单的全部子节点 * */ public List<ITreeModel> children(); /** * 返回当前节点的父节点 * @return 返回当前节点的父亲节点,如果没有父亲节点,返回null * */ public ITreeModel parent(); /** * 返回当前节点的节点类型,这里的节点类型一共有两种,一种是菜单,另外一种是叶子节点 * @return 节点类型 * @see ITreeModel#MENU * @see ITreeModel#LEAF * */ public String nodeType(); /** * 返回当前节点的url * @return 当前节点的url * */ public String url(); /** * 放回当前节点的id * @return 当前节点id * */ public String id(); /** * 返回节点的名字 * @return 节点名字 * */ public String name(); /** * 当前节点如果是菜单,那么该菜单默认是否展开呢?如果是返回true,代表展开;否则,代表不展开 * @return 返回菜单节点的展开状态 * */ public boolean isexpand(); /** * 设置菜单名字 * @param name * */ public void setName(String name); /** * 设置菜单url * @param url * */ public void setUrl(String url); /** * 设置菜单展开状态 * @param isexpend * */ public void setIsexpand(boolean isexpand); /** * 设置父节点 * @param parent * */ public void setParent(ITreeModel parent); /** * 设置孩子节点 * @param children * */ public void setChildren(List<ITreeModel> children); /** * 设置节点id * @param id * */ public void setId(String id); /** * 返回该节点的json数据,包含该节点下面的所有子节点 * @return 返回当前节点的json数据 * */ public String toTreeJson(); /** * 返回从根节点到当前节点的所有节点集合,包含当前节点 * @return 返回根节点到当前节点的集合 * */ public List<ITreeModel> route(); /** * 返回以当前节点为根节点的一棵子树 * @return 返回以当前节点子根节点的子树 * */ public List<ITreeModel> subTree(); }
在上面我们只是定义了一个树模型。并没有实现代码,为什么这样子做呢?因为每个框架使用的数据格式都不一样,所以我们流出扩展性。如果我们不适用ligerui,而使用dwz,我们的接口类不需要发生变化,只需要添加一个dwz树模型子类即可,ligerui相关组件的代码就无需修改了。下面使用ligerui实现一个菜单树。具体代码:
package net.itaem.ligerui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.itaem.vo.MenuVo; import net.sf.json.JSONArray; import net.sf.json.JSONObject; /** * 这里是liger ui树的插件实现类 * @author 骆宏 * @date 2014-08-19 19:39 am * @email [email protected] * */ public class LigerUiTree implements ITreeModel{ //定义一个level,用来保存树的层次 private int level = 0; //定义一个url,用来保存当前节点的url private String url; //定义一个id,用来保存当前节点id private String id; //定义一个isexpend,用来保存节点展开状态 private boolean isexpand; //定义一个name,用来保存节点的名称 private String name; //定义一个parent,用来保存节点的父亲节点 private ITreeModel parent; //定义一个children,用来保存当前节点的所有子节点 private List<ITreeModel> children; public LigerUiTree(){ } /** * 定义一个基本的构造方法,该构造方法的参数都不能为空 * @param id 节点id * @param name 节点name * @param url 节点url * */ public LigerUiTree(String id, String name, String url){ if(id == null || name == null || url == null) throw new RuntimeException("id name url都不能为空"); this.id = id; this.name = name; this.url = url; } public LigerUiTree(String id, String name, String url, ITreeModel parent) { this(id, name, url); this.parent = parent; } public LigerUiTree(String id, String name, String url, List<ITreeModel> children) { this(id, name, url); this.children = children; } @Override public void setUrl(String url) { this.url = url; } @Override public void setId(String id) { this.id = id; } @Override public void setIsexpand(boolean isexpand) { this.isexpand = isexpand; } @Override public void setName(String name) { this.name = name; } public void setParent(ITreeModel parent) { this.parent = parent; } /** * 这里同时会计算树的层次 * 并且这里会同时维护parant - children之间的关联管理,也就是在设置当前节点的子节点时,同时会指点这些子节点的父亲节点为当前节点 * */ public void setChildren(List<ITreeModel> children) { if(children == null) return; //如果为null,do nothing this.children = children; //设置level,遍历所有的children树,然后取最大值 int max = -1; for(int i=0; i<children.size(); i++){ children.get(i).setParent(this); //维护parent-children的相互关联关系 if(children.get(i).level() > max) max = children.get(i).level(); } //如果添加的节点都是叶子节点,那么当前层次为2 //否则计算最大的树层次 = 子节点最大的层次 + 1 if(max != -1 && max != 0){ level += max + 1; }else{ level = 2; } } @Override public int level() { //每次要计算树的高度,都必须遍历整棵树的,然后确定树的高度,由于树随时可以被改变,所以这里不适合使用缓存模式 return level; } @Override public List<ITreeModel> children() { return children; } @Override public ITreeModel parent() { return parent; } @Override public String nodeType() { if(children != null){ //拥有子节点,则代表该节点是菜单 return MENU; }else{ return LEAF; } } @Override public String url() { return url; } @Override public String id() { return id; } @Override public boolean isexpand() { return isexpand; } @Override public String name(){ return name; } @Override public String toTreeJson() { JSONObject json = new JSONObject(); //生成这个节点的基本数据 json.put("text", name); json.put("isexpand", isexpand); json.put("url", url); if(parent != null){ json.put("pid", parent.id()); } //生成这个节点的子菜单数据 JSONArray childrenJson = new JSONArray(); if(children != null){ for(ITreeModel child: children){ //让每个子menu递归的去生成json数据 childrenJson.add(toJson(child)); } json.put("children", childrenJson); } return json.toString(); } /** * 递归入口 * @see MenuVo#toJson() * */ private String toJson(ITreeModel tree){ JSONObject json = new JSONObject(); if(tree.children() != null){ //生成这个菜单节点的基本数据 json.put("text", tree.name()); json.put("id", tree.id()); if(tree.parent() != null){ json.put("pid", tree.parent().id()); } json.put("isexpand", tree.isexpand()); //生成这个菜单节点的子菜单数据 JSONArray childrenJson = new JSONArray(); if(tree.children() != null){ for(ITreeModel child: tree.children()){ //让每个子menu递归的去生成json数据 childrenJson.add(toJson(child)); } json.put("children", childrenJson); } }else{ //这个节点不是菜单,是菜单下面的一个具体子节点,该节点已经没有子节点了 json.put("id", tree.id()); if(tree.parent() != null){ json.put("pid", tree.parent().id()); } json.put("text", tree.name()); json.put("url", tree.url()); } return json.toString(); } @Override public List<ITreeModel> route() { List<ITreeModel> route = new ArrayList<ITreeModel>(); ITreeModel current = this; while(current != null){ route.add(current); current = current.parent(); } java.util.Collections.reverse(route); return route; } @Override public List<ITreeModel> subTree() { List<ITreeModel> subTree = new ArrayList<ITreeModel>(); subTree.add(this); if(this.children == null) return subTree; Iterator<ITreeModel> sti = children.iterator(); while(sti.hasNext()){ ITreeModel tm = sti.next(); subTree.add(tm); if(tm.children() != null) subTree.addAll(tm.subTree()); } return subTree; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + (isexpand ? 1231 : 1237); result = prime * result + level; result = prime * result + (name == null? 0 : name.hashCode()); result = prime * result + ((url == null) ? 0 : url.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LigerUiTree other = (LigerUiTree) obj; if (id == null) { if (other.id() != null) return false; } else if (!id.equals(other.id())) return false; if (isexpand != other.isexpand()) return false; if (level != other.level()) return false; if(name == null){ if(other.name() != null) return false; } if (url == null) { if (other.url() != null) return false; } else if (!url.equals(other.url())) return false; return true; } /** * 返回节点的基本信息 * @return * */ @Override public String toString() { return "LigerUiTree [name=" + name + ", level=" + level + ", url=" + url + ", id=" + id + ", nodeType=" + nodeType() + ", isexpand=" + isexpand + "]"; } }
在LigerUiTree中,我们使用了递归来遍历树。这个代码不难,就是普通的一些树的常用操作。为了测试,我们需要一个工具类,用来输出这个树的数据结果:
package net.itaem.ligerui; import java.util.List; /** * * 这个类用来输出树的结构信息,为了方便测试 * @author 骆宏 * @date 2014-08-19 * @email [email protected] * */ public class LigerUiTreeTool { /** * 以下面个数来输出树 * root * --child1 * --child2 * --child-child1 * --child-child2 * --child-3 * */ public static void printTree(ITreeModel tree){ if(tree == null) return; if(tree.parent() == null){ //输出根节点的名字 System.out.println(tree.name()); } List<ITreeModel> children = tree.children(); if(children != null){ level++; for(int i=0; i<children.size(); i++){ for(int j=0; j<level; j++){ System.out.print(" "); } System.out.println("--" + children.get(i).name()); if(children.get(i).children() != null) printTree(children.get(i)); else if(i == children.size()-1){ level = 1; } } } } //用来辅助输出的一个level,用来计算输出空格 private static int level = 1; }
上面的类是用于输出树的,没有大的实际意义。在这个工具树种,我们使用了广度遍历。一般情况下,树的遍历有两种。一种是广度遍历,另外一种则是深度遍历。大家有兴趣可以百度下相关定义以及概念。
代码差不多了,直接写个测试吧。
package net.itaem.test; import java.util.ArrayList; import java.util.List; import net.itaem.ligerui.ITreeModel; import net.itaem.ligerui.LigerUiTree; import net.itaem.ligerui.LigerUiTreeTool; /** * 测试liger ui的树模型 * */ public class LigerUiTreeTest { public static void main(String[] args){ ITreeModel root = new LigerUiTree("ROOT", "根节点", "localhost/root"); List<ITreeModel> children = new ArrayList<ITreeModel>(); //生成一个1 * 10 * 3三个层次的树模型 for(int i=0; i<10; i++){ ITreeModel child = new LigerUiTree("child-" + i, "child-name-" + i, "localhost/child-" + i); List<ITreeModel> childChildren = new ArrayList<ITreeModel>(); for(int j=0; j<3; j++){ ITreeModel childChild = new LigerUiTree("children-" + i + "" + j, "child-children-child-" + i + "" + j, "localhost/child/children-" + i + "" + j); childChildren.add(childChild); } child.setChildren(childChildren); children.add(child); } root.setChildren(children); System.out.println("======================whole tree======================="); LigerUiTreeTool.printTree(root); System.out.println("=======================基本信息================================"); System.out.println(root); System.out.println("==========================subTree=============================="); //输出根节点的子树 List<ITreeModel> subTree = root.children().get(0).subTree(); System.out.println(subTree); System.out.println("==========================route================================"); //测试叶子节点到根节点的所有节点 ITreeModel leaf = root.children().get(0).children().get(0); System.out.println(leaf.route()); System.out.println("==========================after change========================="); //测试修改名字,url,name,id,isexpend等属性 root.setName("change name"); root.setId("change id"); root.setIsexpand(true); root.setUrl("change url"); System.out.println(root); System.out.println("==============================liger ui tree json=========================="); System.out.println(root.toTreeJson()); } }
下面是程序输出结果:
======================whole tree======================= 根节点 --child-name-0 --child-children-child-00 --child-children-child-01 --child-children-child-02 --child-name-1 --child-children-child-10 --child-children-child-11 --child-children-child-12 --child-name-2 --child-children-child-20 --child-children-child-21 --child-children-child-22 --child-name-3 --child-children-child-30 --child-children-child-31 --child-children-child-32 --child-name-4 --child-children-child-40 --child-children-child-41 --child-children-child-42 --child-name-5 --child-children-child-50 --child-children-child-51 --child-children-child-52 --child-name-6 --child-children-child-60 --child-children-child-61 --child-children-child-62 --child-name-7 --child-children-child-70 --child-children-child-71 --child-children-child-72 --child-name-8 --child-children-child-80 --child-children-child-81 --child-children-child-82 --child-name-9 --child-children-child-90 --child-children-child-91 --child-children-child-92 =======================基本信息================================ LigerUiTree [name=根节点, level=3, url=localhost/root, id=ROOT, nodeType=menu, isexpand=false] ==========================subTree============================== [LigerUiTree [name=child-name-0, level=2, url=localhost/child-0, id=child-0, nodeType=menu, isexpand=false], LigerUiTree [name=child-children-child-00, level=0, url=localhost/child/children-00, id=children-00, nodeType=leaf, isexpand=false], LigerUiTree [name=child-children-child-01, level=0, url=localhost/child/children-01, id=children-01, nodeType=leaf, isexpand=false], LigerUiTree [name=child-children-child-02, level=0, url=localhost/child/children-02, id=children-02, nodeType=leaf, isexpand=false]] ==========================route================================ [LigerUiTree [name=根节点, level=3, url=localhost/root, id=ROOT, nodeType=menu, isexpand=false], LigerUiTree [name=child-name-0, level=2, url=localhost/child-0, id=child-0, nodeType=menu, isexpand=false], LigerUiTree [name=child-children-child-00, level=0, url=localhost/child/children-00, id=children-00, nodeType=leaf, isexpand=false]] ==========================after change========================= LigerUiTree [name=change name, level=3, url=change url, id=change id, nodeType=menu, isexpand=true]
哈哈,测试完毕,然后就可以使用Action,将树的数据携带到jsp中,那么一个ligerui的树插件就搞定了。
liger ui组件的抽取与封装