接着上一篇博客继续讲,这里我们要实现树节点的多级联动,效果如下所示
其实我们应该想到:理论上树的层级是无限的,如果想要实现多级联动,只有递归这一条路能走。我们先来看几种情况:
情况一
根节点“中国”被选中,那么此时“中国”节点下的所有子节点都应该被选中,换句话说这种情况只需要以“中国”为出发节点向下递归就行了。
情况二
如果此时选中“浙江省”节点,那么首先“浙江省”及其以下的子节点将会被全部选中,同时又由于“江苏省”节点也已经被选中,因此在“浙江省”和“江苏省”都被选中的情况下“中国”节点也需要被选中,换句话说这种情况需要进行两次递归:第一次递归是向下递归,目的是选中该节点及其以下的子节点,第二次递归则是向上递归,目的是判断该节点以上的根节点是否需要被选中。
情况三
情况三其实跟情况二差不多:如果此时选中“湖州市”,考虑到“杭州市”已经被选中,那么“浙江省”也应该被选中,又考虑到“江苏省”已经被选中,那么“中国”也应该被选中。
情况四
如果撤销对“中国”节点的选中状态,那么此时“中国”及其下属的子节点都应该被撤销选中状态,换句话说这种情况只需要以“中国”为起点向下递归即可。
情况五
如果撤销“拱墅区”节点的选中状态,则“杭州市”节点的选中状态也应该撤销,而这也将进一步导致“浙江省”和“中国”这两个节点的选中状态被撤销,换句话说这种情况需要两次递归:第一次是向下递归,目的是撤销该节点及其子节点的选中状态,第二次是向上递归,目的是找到根节点逐一撤销其选中状态。
主要用到的事件和方法
// 选中节点
onNodeChecked: function (event, node) {
},
// 撤销节点选中
onNodeUnchecked: function (event, node) {
}
// 获取父节点
var parent = $('#tv').treeview('getParent', node);
// 根据nodeId选中某节点
$('#tv').treeview('checkNode', [node.nodeId, { silent: true }]);
// 根据nodeId撤销某节点的选中状态
$('#tv').treeview('uncheckNode', [node.nodeId, { silent: true }]);
// 获取当前处于被选中状态的所有节点
var checkedNodes = $('#tv').treeview('getChecked');
// 获取与某节点处于平级关系的兄弟节点
var brotherNodes = $('#tv').treeview('getSiblings', node);
前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Bootstrap TreeView</title>
<link href="Content/bootstrap.min.css" rel="stylesheet" />
<link href="Content/bootstrap-treeview.min.css" rel="stylesheet" />
<script src="Scripts/jquery-2.1.4.min.js"></script>
<script src="Scripts/bootstrap.min.js"></script>
<script src="Scripts/bootstrap-treeview.min.js"></script>
</head>
<body>
<div id="tv" style="width:300px;border:1px solid #E8E8E8;margin-left:200px;margin-top:50px;"></div>
<script>
$(document).ready(function () {
var nodeData = [];
// 获取后台数据
$.ajax({
url: 'Handlers/GetTreeNodesHandler.ashx',
type: 'post',
dataType: 'json',
async: false,
success: function (data) {
nodeData = data;
}
})
// 初始化TreeView
$('#tv').treeview({
data: nodeData,
showCheckbox: true,
showBorder: false,
selectedBackColor: 'skyblue',
selectedColor: 'white',
onNodeSelected: function (event, node) {
alert(node.text);
},
onNodeChecked: function (event, node) {
check(node);
},
onNodeUnchecked: function (event, node) {
uncheck(node);
}
})
})
// 选择节点
function check(node) {
var root = $('#tv').treeview('getParent', node);
if (root.nodeId == undefined) {
checkChildrenNodes(node);
}
else {
checkChildrenNodes(node);
checkParentNodes(node);
}
}
// 取消选择节点
function uncheck(node) {
var root = $('#tv').treeview('getParent', node);
if (root.nodeId == undefined) {
uncheckChildrenNodes(node);
}
else {
uncheckChildrenNodes(node);
uncheckParentNodes(node);
}
}
// 向下递归:选择某节点及以下的全部子节点
function checkChildrenNodes(node) {
if (node.nodes == null) {
$('#tv').treeview('checkNode', [node.nodeId, { silent: true }]);
}
else {
for (var i = 0; i < node.nodes.length; i++) {
$('#tv').treeview('checkNode', [node.nodeId, { silent: true }]);
checkChildrenNodes(node.nodes[i]);
}
}
}
// 向上递归:判断某节点以上的根节点是否需要被选择
function checkParentNodes(node) {
var checkedNodes = $('#tv').treeview('getChecked');
var brotherNodes = $('#tv').treeview('getSiblings', node);
if (brotherNodes.length > 0) {
var checked = [];
for (var i = 0; i < brotherNodes.length; i++) {
for (var j = 0; j < checkedNodes.length; j++) {
if (brotherNodes[i].nodeId == checkedNodes[j].nodeId) {
checked.push(true);
}
}
}
if (checked.length == brotherNodes.length) {
var parent = $('#tv').treeview('getParent', node);
$('#tv').treeview('checkNode', [parent.nodeId, { silent: true }]);
checkParentNodes(parent);
}
}
}
// 向下递归:取消选择某节点及以下的全部子节点
function uncheckChildrenNodes(node) {
if (node.nodes == null) {
$('#tv').treeview('uncheckNode', [node.nodeId, { silent: true }]);
}
else {
for (var i = 0; i < node.nodes.length; i++) {
$('#tv').treeview('uncheckNode', [node.nodeId, { silent: true }]);
uncheckChildrenNodes(node.nodes[i]);
}
}
}
// 向上递归:取消选择某节点以上的全部根节点
function uncheckParentNodes(node) {
var parent = $('#tv').treeview('getParent', node);
if (parent.nodeId != undefined) {
$('#tv').treeview('uncheckNode', [parent.nodeId, { silent: true }]);
uncheckParentNodes(parent);
}
}
</script>
</body>
</html>
后台代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using Newtonsoft.Json;
namespace WebApplication2.Handlers
{
/// <summary>
/// GetTreeNodesHandler 的摘要说明
/// </summary>
public class GetTreeNodesHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
// 查询数据
DataTable dataTable = new DataTable();
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ToString();
using (SqlDataAdapter adapter = new SqlDataAdapter("select * from [TRegion]", connectionString))
{
adapter.Fill(dataTable);
}
// 转换为实体类
List<Node> nodes = new List<Node>();
foreach (DataRow row in dataTable.Rows)
{
Node node = new Node();
node.id = Convert.ToInt32(row["id"].ToString());
node.pid = Convert.ToInt32(row["pid"].ToString());
node.text = row["text"].ToString();
nodes.Add(node);
}
// 转换为JSON树
List<Node> list = CreateTreeNodes(nodes);
context.Response.Write(JsonConvert.SerializeObject(list).Replace("[]", "null"));
}
public bool IsReusable
{
get
{
return false;
}
}
// 生成树
private List<Node> CreateTreeNodes(List<Node> nodes)
{
List<Node> root = nodes.FindAll(node => node.pid == 0);
return SortNodes(nodes, root);
}
// 递归分组
private List<Node> SortNodes(List<Node> nodes, List<Node> root)
{
for (int i = 0; i < root.Count; i++)
{
List<Node> children = nodes.FindAll(node => node.pid == root[i].id);
SortNodes(nodes, children);
root[i].nodes = children;
}
return root;
}
}
public class Node
{
/// <summary>
/// 编号
/// </summary>
public int id { get; set; }
/// <summary>
/// 上一级编号
/// </summary>
public int pid { get; set; }
/// <summary>
/// 名称
/// </summary>
public string text { get; set; }
/// <summary>
/// 子节点
/// </summary>
public List<Node> nodes;
}
}
到此为止,无限层级树节点的多级联动效果也实现了。