jQuery EasyUI-非同步樹後臺程式碼與資料庫設計
easyui的非同步樹建立很簡單,只需要指定一個獲取樹的JSON資料的URL地址就可以了,API是這樣寫的:
非同步樹後臺程式碼設計方式有很多種,我說下我的設計。
資料庫表設計
Tree表
先來解釋下每個欄位的含義:
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
這也是正常應該存在的情況,此時該節點在頁面上顯示為一個未展開的資料夾形態,但是此時該節點下的子節點(或者子子節點,根據子節點屬性按需求遞迴,以此類推)已經載入完畢,這時候展開該節點,會顯示已經載入好的子節點,因此並不會向後臺傳送請求。
實體類程式碼及實體類屬性轉換為JSON方法程式碼
根據資料庫設計及上面分析的情況,可以看出1、2都作為情況1處理,3、4為一種情況,5、6為一種情況,7為一種情況,8為一種情況。
- 實體類TreeNode
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<TreeNode> treeNodes;
/**節點屬性集合*/
private List<TreeNodeAttr> 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<TreeNodeAttr> 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 構造方法略
- 實體類TreeNodeAttr
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);
}
- TreeMapper.xml
<mapper namespace="com.bc.dao.tree.TreeMapper">
<select id="selectTreeNodeInfo" resultType="TreeNode" parameterType="TreeNode">
select * FROM tree where id = #{id}
</select>
<resultMap id="treeNodeAttrs" type="TreeNode">
<collection property="treeNodeAttrs" ofType="TreeNodeAttr">
<result property="name" column="name"/>
<result property="value" column="value"/>
</collection>
</resultMap>
<select id="selectTreeNodeAttrs" resultMap="treeNodeAttrs" parameterType="TreeNode">
select name,value from tree_attributes where tid = #{id}
</select>
<resultMap id="treeNodeChildrens" type="TreeNode">
<collection 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"/>
</collection>
</resultMap>
<select id="selectChildrenTreeNodesInfo" resultMap="treeNodeChildrens" parameterType="TreeNode">
select id,pid,text,iconCls,state,checked,attributes,children,loadChildren,layer from tree where pid = #{id}
</select>
</mapper>
業務邏輯層
- 介面
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<TreeNode> getTreeNodeChildrens(List<TreeNode> 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的屬性、事件和方法實現一些效果
連結地址:待編輯