接着上一篇博客繼續講,這裏我們要實現樹節點的多級聯動,效果如下所示
其實我們應該想到:理論上樹的層級是無限的,如果想要實現多級聯動,只有遞歸這一條路能走。我們先來看幾種情況:
情況一
根節點“中國”被選中,那麼此時“中國”節點下的所有子節點都應該被選中,換句話說這種情況只需要以“中國”爲出發節點向下遞歸就行了。
情況二
如果此時選中“浙江省”節點,那麼首先“浙江省”及其以下的子節點將會被全部選中,同時又由於“江蘇省”節點也已經被選中,因此在“浙江省”和“江蘇省”都被選中的情況下“中國”節點也需要被選中,換句話說這種情況需要進行兩次遞歸:第一次遞歸是向下遞歸,目的是選中該節點及其以下的子節點,第二次遞歸則是向上遞歸,目的是判斷該節點以上的根節點是否需要被選中。
情況三
情況三其實跟情況二差不多:如果此時選中“湖州市”,考慮到“杭州市”已經被選中,那麼“浙江省”也應該被選中,又考慮到“江蘇省”已經被選中,那麼“中國”也應該被選中。
情況四
如果撤銷對“中國”節點的選中狀態,那麼此時“中國”及其下屬的子節點都應該被撤銷選中狀態,換句話說這種情況只需要以“中國”爲起點向下遞歸即可。
情況五
如果撤銷“拱墅區”節點的選中狀態,則“杭州市”節點的選中狀態也應該撤銷,而這也將進一步導致“浙江省”和“中國”這兩個節點的選中狀態被撤銷,換句話說這種情況需要兩次遞歸:第一次是向下遞歸,目的是撤銷該節點及其子節點的選中狀態,第二次是向上遞歸,目的是找到根節點逐一撤銷其選中狀態。
主要用到的事件和方法
// 選中節點
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;
}
}
到此爲止,無限層級樹節點的多級聯動效果也實現了。