项目结构:
项目演示:
技术要点:
1.3.2 技术要点
在基本原理的介绍中,了解到通过在父节点内动态创建子节点,并利用样式表缩进完成树形列表
的基本框架。除了这一点外,还有下面一些问题需要考虑。
1 .将父节点所有的子节点放入一个容器中
基本原理页面中仅包含了创建子节点的功能,不可以将创建好的节点再进行关闭操作。实际应用
中菜单总是包含打开和关闭两种操作。为了方便进行关闭操作,将子节点放入一个容器中,在关闭时
只需要设置容器的显示属性即可。
2 .节点开关的具体实现
每个父节点的所有子节点放入容器中后,在单击父节点时判断子节点容器的显示状态。如果当前
为打开状态,则将其关闭,反之则将其打开。具体使用的方法是调用节点的 style.display 属性进行判断,
关闭时的状态为 none,非 none 时表示打开。为了清晰地表示父节点的打开或关闭状态,使用“+”和
“-”字符进行标识。
3 .节点分为目录节点和非目录节点
目录节点下可包含子节点,非目录节点也可称为叶子节点,即该节点不包含子节点。在本例中对
这两种节点进行区别对待。单击目录节点将对其子节点进行展开或关闭操作。单击非目录节点将链接
到新的页面(目前暂时将新页面地址弹出,在实际应用中可进行修改)。
数据库:
CREATE TABLE `tree` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键‘, `text` varchar(255) NOT NULL COMMENT ‘显示文本‘, `isfolder` varchar(5) NOT NULL DEFAULT ‘false‘ COMMENT ‘是否含有下级目录‘, `link` varchar(255) NOT NULL COMMENT ‘连接‘, `pid` int(11) NOT NULL DEFAULT ‘0‘ COMMENT ‘父id‘, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
GetTreeByParentId.java:获取节点信息servlet:
package com.gordon.servlet; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gordon.util.DBUtils; /** * Servlet implementation class GetTreeByParentId */ @WebServlet("/GetTreeByParentId") public class GetTreeByParentId extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public GetTreeByParentId() { super(); // TODO Auto-generated constructor stub } /** * @see Servlet#init(ServletConfig) */ public void init(ServletConfig config) throws ServletException { // TODO Auto-generated method stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/xml;charset=utf-8"); String parentId = request.getParameter("parentId"); // 获取要加载的节点编号 // 创建用于保存 xmlTree 信息的 StringBuffer 对象 StringBuffer xmlTree = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); xmlTree.append("<tree>"); // xmlTree 根节点为<tree> /* * 根据请求的目标节点返回不同的结果 isFolder 属性标识当前节点是否为目录,true 表示目录,false 表示普通节点 link * 属性用于设置普通节点的目标链接地址 */ String sql = "select * from tree where pid = ?"; // 定义查询数据库的 SQL 语句 Connection conn = null; // 声明 Connection 对象 PreparedStatement pstmt = null; // 声明 PreparedStatement 对象 ResultSet rs = null; // 声明 ResultSet 对象 try { conn = DBUtils.getConnection(); // 获取数据库连接 pstmt = conn.prepareStatement(sql); // 创建 PreparedStatement pstmt.setString(1, parentId); // 设置参数 rs = pstmt.executeQuery(); // 执行查询,返回结果集 while (rs.next()) { // 遍历结果集创建 item 节点 xmlTree.append("<item id=\""); xmlTree.append(rs.getString("id")); xmlTree.append("\" isFolder=\""); xmlTree.append(rs.getString("isfolder")); String link = rs.getString("link"); // 当 link 字段数据存在时才加入 link 属性信息 if (link != null && !"".equals(link)) { xmlTree.append("\" link=\""); xmlTree.append(link); } xmlTree.append("\">"); xmlTree.append(rs.getString("text")); xmlTree.append("</item>"); } } catch (ClassNotFoundException cnfe) { System.out.println(cnfe.toString()); } catch (SQLException e) { System.out.println(e.toString()); } try { rs.close(); // 关闭结果集 pstmt.close(); // 关闭 PreparedStatement conn.close(); // 关闭连接 } catch (Exception e) { System.out.println(e.toString()); } xmlTree.append("</tree>"); // xmlTree 根节点的结束标签 System.out.println(xmlTree.toString()); response.getWriter().print(xmlTree.toString()); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }
DBUtil.java:数据库连接:
package com.gordon.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtils { private static final String URL = "jdbc:mysql://localhost:3306/ajaxexample_3"; private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String USERNAME = "root"; private static final String PASSWORD = "root"; public static Connection getConnection() throws ClassNotFoundException, SQLException { Class.forName(DRIVER); return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }
tree.css:
/* 子节点容器 box 需要缩进 */ div.box { margin-left: 20px; } /* 目录节点标识样式 */ span.folderMark { font-family: "宋体"; color: #F00; cursor: hand; margin-right: 5px; } /* 目录节点样式 */ span.folder { cursor: hand; } /* 非目录节点标识样式 */ span.itemMark { font-family: "宋体"; color: #F00; margin-right: 5px; } /* 非目录节点样式 */ span.item { cursor: hand; }
tree.js:
// 创建Tree对象 var Tree = new function() { this._url = "GetTreeByParentId"; // 用于请求数据的服务器页面地址 this._openMark = "-"; // 目录节点处于展开状态时的标识 this._closeMark = "+"; // 目录节点处于关闭状态时的标识 this._itemMark = "·"; // 非目录节点标识 this._initId = "treeInit"; // 树形目录初始 div 标识 this._rootData = "根目录"; // 根节点文字信息 this._boxSuffix = "_childrenBox"; // 子节点容器后缀 this._folderType = "folder"; // 目录节点类型变量 this._itemType = "item"; // 非目录节点类型变量 // 初始化根节点 this.init = function() { var initNode = document.getElementById(this._initId); // 获取初始 div var _node = document.createElement("div"); // 创建新 div 作为根节点 _node.id = "0"; // 根节点 id 为 0 _node.innerHTML = this.createItemHTML(_node.id, this._folderType, this._rootData); initNode.appendChild(_node); // 将根节点加入初始 div } // 获取给定节点的子节点 this.getChildren = function(_parentId) { // 获取页面子节点容器 box var childBox = document.getElementById(_parentId + this._boxSuffix); // 如果子节点容器已存在,则直接设置显示状态,否则从服务器获取子节点信息 if (childBox) { var isHidden = (childBox.style.display == "none"); // 判断当前状态是否隐藏 // 隐藏则显示,如果显示则变为隐藏 childBox.style.display = isHidden ? "" : "none"; // 根据子节点的显示状态修改父节点标识 var _parentNode = document.getElementById(_parentId); _parentNode.firstChild.innerHTML = isHidden ? this._openMark : this._closeMark; } else { var xmlHttp = this.createXmlHttp(); // 创建 XmlHttpRequest 对象 xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { // 调用 addChildren 函数生成子节点 Tree.addChildren(_parentId, xmlHttp.responseXML); } } xmlHttp.open("GET", this._url + "?parentId=" + _parentId, true); xmlHttp.send(null); } } // 根据获取的 xmlTree 信息,设置指定节点的子节点 this.addChildren = function(_parentId, _data) { var _parentNode = document.getElementById(_parentId); // 获取父节点 _parentNode.firstChild.innerHTML = this._openMark;// 设置节点前标记为目录展开形式 // 创建一个容器,称为 box,用于存放所有子节点 var _nodeBox = document.createElement("div"); // 容器的 id 规则为:在父节点 id 后加固定后缀 _nodeBox.id = _parentId + this._boxSuffix; _nodeBox.className = "box"; // 样式名称为 box,div.box 样式会对此节点生效 _parentNode.appendChild(_nodeBox); // 将子节点 box 放入父节点中 // 获取所有 item 节点 var _children = _data.getElementsByTagName("tree")[0].childNodes; var _child = null; // 声明_child 变量用于保存每个子节点 var _childType = null; // 声明_childType 变量用于保存每个子节点类型 for (var i = 0; i < _children.length; i++) { // 循环创建每个子节点 _child = _children[i]; _node = document.createElement("div"); // 每个节点对应一个新 div _node.id = _child.getAttribute("id"); // 节点的 id 值就是获取数据中的 id 属性值 // 设置子节点类型 _childType = _child.getAttribute("isFolder") == "true" ? this._folderType : this._itemType; // 根据节点类型不同,调用 createItemHTML 创建节点内容 if (_childType == this._itemType) { // 非目录节点在最后多传一个 link 数据,用于单击后链接到新页面 _node.innerHTML = this.createItemHTML(_node.id, _childType, _child.firstChild.data, _child.getAttribute("link")); } else { // 目录节点只需传递 id,节点类型,节点数据 _node.innerHTML = this.createItemHTML(_node.id, _childType, _child.firstChild.data); } _nodeBox.appendChild(_node); // 将创建好的节点加入子节点 box 中 } } // 创建节点的页面片断 this.createItemHTML = function(itemId, itemType, itemData, itemLink) { // 根据节点类型不同,返回不同的 HTML 片断 if (itemType == this._itemType) { // 非目录节点的 class 属性以 item 开头,并且 onclick 事件调用 Tree.clickItem 函数 return ‘<span class="itemMark">‘ + this._itemMark + ‘</span>‘ + ‘<span class="item" onclick="Tree.clickItem(\‘‘ + itemLink + ‘\‘);">‘ + itemData + ‘</span>‘; } else if (itemType == this._folderType) { // 目录节点的 class 属性以 folder 开头,并且 onclick 事件调用 Tree.getChildren 函数 return ‘<span class="folderMark" onclick="Tree.getChildren(\‘‘ + itemId + ‘\‘)">‘ + this._closeMark + ‘</span>‘ + ‘<span class="folder" onclick="Tree.getChildren(\‘‘ + itemId + ‘\‘)">‘ + itemData + ‘</span>‘ } } // 单击叶子节点后的动作,目前只是弹出对话框,可修改为链接到具体的页面 this.clickItem = function(_link) { alert("当前节点可以链接到页面 " + _link + " 。"); } // 用于创建 XMLHttpRequest 对象 this.createXmlHttp = function() { var xmlHttp = null; // 根据 window.XMLHttpRequest 对象是否存在使用不同的创建方式 if (window.XMLHttpRequest) { xmlHttp = new XMLHttpRequest(); // FireFox、Opera 等浏览器支持的创建方式 } else { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); // IE 浏览器支持的创建方式 } return xmlHttp; } }
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>动态树形列表</title> <script type="text/javascript" src="js/tree.js"></script> <link href="css/tree.css" type="text/css" rel="stylesheet"> </head> <body > <h1>动态树形列表</h1> <div id="treeInit"></div> </body> </html>
时间: 2024-11-18 17:36:28