拨号项目中,虽然自己也鼓捣个树形结构,但思路却不是很清晰,而且是利用树形结构信息是存在xml文件中的。看完drp,并不难,但感触最深的是王勇老师分析问题,把解决问题的思路清晰化。
对树形结构,从数据表、web前端div生成树原理,再到对树形信息的增删改查,由两边向中间逐层分析。
数据库设计
树的几种设计方式
- 不带冗余字段,id,pid。效率低,查找相对繁琐。
- 带冗余字段,id,pid,isleaf,childrencount。多冗余字段,但方便查找。
- 采用固定字符串,00010010001。不易理解,编程复杂。
- 00,所有分销商
- 01,华北区
- 001,北京市
- 0001,北京医药股份有限公司
这里采用第二种,带冗余字段id,pid。
自关联,id为主键,pid(parent
id)为外键
div生成树的原理
原理图:
html
<div>
<img alt="展开"style="cursor:hand;" onClick="display('1');"
id="img1"src="../images/plus.gif">
<imgid="im1" src="../images/closedfold.gif">
<a href="client_node_crud.html"target="clientDispAreaFrame">所有分销商</a>
<divstyle="display:none;" id="div1">
<div>
<imgsrc="../images/white.gif">
<img alt="展开"style="cursor:hand;" onClick="display('2');"
id="img2"src="../images/plus.gif">
<imgid="im2" src="../images/closedfold.gif">
<a href="client_node_crud.html"target="clientDispAreaFrame">华北区</a>
<divstyle="display:none;" id="div2">
<div>
<imgsrc="../images/white.gif">
<imgsrc="../images/white.gif">
<img alt="展开"style="cursor:hand;" onClick="display('3');"
id="img3"src="../images/plus.gif">
<imgid="im3" src="../images/closedfold.gif">
<a href="client_node_crud.html"target="clientDispAreaFrame">北京市</a>
<divstyle="display:none;" id="div3">
<div>
<imgsrc="../images/white.gif">
<imgsrc="../images/white.gif">
<imgsrc="../images/white.gif">
<imgsrc="../images/minus.gif">
<imgsrc="../images/openfold.gif">
<a href="client_crud.html"target="clientDispAreaFrame">北京医药股份有限公司</a>
</div>
</div>
</div>
</div>
</div>
<div>
<imgsrc="../images/white.gif">
<imgsrc="../images/minus.gif">
<imgsrc="../images/openfold.gif">
<a href="client_node_crud.html"target="clientDispAreaFrame">东北区</a>
</div>
<div>
<imgsrc="../images/white.gif">
<imgsrc="../images/minus.gif">
<imgsrc="../images/openfold.gif">
<a href="client_node_crud.html"target="clientDispAreaFrame">华南区</a>
</div>
</div>
</div>
js控制
functiondisplay(id) {
eval("var div=div"+id);
eval("var img=img"+id);
eval("var im=im"+id);
div.style.display=div.style.display=="block"?"none":"block";
img.src=div.style.display=="block"?"../images/minus.gif":"../images/plus.gif";
im.src=div.style.display=="block"?"../images/openfold.gif":"../images/closedfold.gif";
img.alt=div.style.display=="block"?"关闭":"展开";
}
树的读取
id,pid递归读取
ClientTreeReader.java
/**
* 完成分销商树的递归读取
* @author Administrator
*
*/
publicclass ClientTreeReader {
privateStringBuffer sbTreeHTML = new StringBuffer();
/**
* 返回HTML字符串
* @return
*/
publicString getClientTreeHTMLString() {
Connectionconn = null;
try {
conn= DbUtil.getConnection();
readClientTree(conn,0, 0);
}catch(Exceptione) {
e.printStackTrace();
}finally{
DbUtil.close(conn);
}
returnsbTreeHTML.toString();
}
/**
* 递归读取分销商树
*
*第四步: 采用div生成树形结构
* @param conn
* @param id
* @param level 控制层次
*/
privatevoid readClientTree(Connection conn, int id, int level)
throwsSQLException {
Stringsql = "select * from t_client where pid=?";
PreparedStatementpstmt = null;
ResultSetrs = null;
try {
pstmt= conn.prepareStatement(sql);
pstmt.setInt(1,id);
rs =pstmt.executeQuery();
while(rs.next()) {
sbTreeHTML.append("<div>");
sbTreeHTML.append("\n");// \n html代码换行显示 <br>布局换行显示
for(int i=0; i<level; i++) {
sbTreeHTML.append("<imgsrc=\"../images/white.gif\">");
sbTreeHTML.append("\n");
}
//if("N".equals(rs.getString("is_leaf"))) {
if(Constants.NO.equals(rs.getString("is_leaf"))) {
// +
sbTreeHTML.append("<imgalt=\"展开\" style=\"cursor:hand;\"onClick=\"display('" + rs.getInt("id") + "');\"id=\"img" + rs.getInt("id") + "\"src=\"../images/plus.gif\">");
sbTreeHTML.append("\n");
//关闭的文件夹
sbTreeHTML.append("<imgid=\"im" + rs.getInt("id") + "\"src=\"../images/closedfold.gif\">");
sbTreeHTML.append("\n");
// 文本
sbTreeHTML.append("<ahref=\"client_node_crud.jsp?id=" + rs.getInt("id") +"\" target=\"clientDispAreaFrame\">" +rs.getString("name") + "</a>");
sbTreeHTML.append("\n");
//递归孩子区域
sbTreeHTML.append("<divstyle=\"display:none;\" id=\"div" +rs.getInt("id") + "\">");
sbTreeHTML.append("\n");
readClientTree(conn,rs.getInt("id"), level + 1);
sbTreeHTML.append("</div>");
sbTreeHTML.append("\n");
}else{
sbTreeHTML.append("<imgsrc=\"../images/minus.gif\">");
sbTreeHTML.append("\n");
sbTreeHTML.append("<imgsrc=\"../images/openfold.gif\">");
sbTreeHTML.append("\n");
//如果是分销商
if(Constants.YES.equals(rs.getString("is_client"))) {
sbTreeHTML.append("<ahref=\"client_crud.jsp?id=" + rs.getInt("id") +"\" target=\"clientDispAreaFrame\">" +rs.getString("name") + "</a>");
}else{
//如果是区域
sbTreeHTML.append("<ahref=\"client_node_crud.jsp?id=" + rs.getInt("id") +"\" target=\"clientDispAreaFrame\">" +rs.getString("name") + "</a>");
}
sbTreeHTML.append("\n");
}
sbTreeHTML.append("</div>");
sbTreeHTML.append("\n");
}
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
}
}
}
增加节点
删除节点
clientManager.java
/**
* 删除分销商或者区域
* @param id
*/
publicvoid delClientOrRegion(int id){
Connectionconn = null;
try{
conn= DbUtil.getConnection();
//开启手动事务
DbUtil.beginTransaction(conn);
//保存当前节点信息(子方法应该向上抛出异常,否则事务不能发现异常,也就不能进行回滚)
ClientcurrentNode = findClientOrRegionById(id);
//递归删除(包括当前节点)(子方法应该向上抛出异常,否则事务不能发现异常,也就不能进行回滚)
recursionDelNode(conn,id);
//当前节点的父节点如果没有其他孩子,则为叶子节点(子方法应该向上抛出异常,否则事务不能发现异常,也就不能进行回滚)
if(getChildrenCount(conn, currentNode.getPid()) == 0) {
modifyIsLeafField(conn,currentNode.getPid(), Constants.YES);
}
//提交事务
DbUtil.commitTransaction(conn);
}catch (Exception e) {
e.printStackTrace();
//回滚事务
DbUtil.rollbackTransaction(conn);
}finally {
//恢复事务状态
DbUtil.resetConnection(conn);
DbUtil.close(conn);
}
}
/**
* 递归删除
* @param conn
* @param id
* @throws SQLException
*/
privatevoid recursionDelNode(Connection conn, int id) throws SQLException{
Stringsql = "select * from t_client where pid=?";
PreparedStatementpstmt = null;
ResultSetrs = null;
try{
pstmt= conn.prepareStatement(sql);
pstmt.setInt(1,id);
//得到当前节点的子节点
rs= pstmt.executeQuery();
//删除子节点
while(rs.next()) {
//如果当前节点的子节点不是叶子,继续向下找
if(Constants.NO.equals(rs.getString("is_leaf"))) {
recursionDelNode(conn,rs.getInt("id"));
}else {
//如果当前节点的子节点为叶子,直接删除
delNode(conn,rs.getInt("id"));
}
}
//删除当前节点
delNode(conn,id);
} finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
}
}
/**
* 删除节点
* @param conn
* @param id
* @throws SQLException
*/
privatevoid delNode(Connection conn, int id) throws SQLException{
Stringsql = "delete t_client where id=?";
PreparedStatementpstmt = null;
try{
pstmt= conn.prepareStatement(sql);
pstmt.setInt(1,id);
pstmt.executeUpdate();
} finally {
DbUtil.close(pstmt);
}
}
待完善
信息更改后,页面刷新,不能恢复修改前的状态
刷新js:window.parent.clientTreeFrame.location.reload();
在开发时,树形目录结构可能不用我们从头开始做,不用自己实现,比如.net中有treeview控件,jquery中有相应的插件,但明白它的原理还是很重要的,况且也挺有意思滴。