zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。大家可以从 zTree 的官网下载和学习。
我这里通过自己的实践,简单介绍一下在.net MVC 环境下,如何实现 ztree 展示和管理树型结构的。
首先我们先假设一下任务,管理新闻的分类,先介绍一下数据结构。
我们还是先建立一个表结构对象和访问控制类
//新闻分类(VON_Catalog) #region "新闻分类信息类声明" namespace VonPortal.Web.Models { /// <summary>新闻分类 信息类</summary> public class CatalogInfo { #region "Constructors" /// <summary> /// 构造函数 /// </summary> public CatalogInfo() { } /// <summary> /// 含初始化构造函数 /// </summary> /// <param name="ID">序号</param> /// <param name="CatalogName">分类名称</param> /// <param name="PID">父类序号</param> /// <param name="DisplayOrder">显示序号</param> /// <param name="Note">分类说明</param> public CatalogInfo(int ID, string CatalogName, int PID, int DisplayOrder, string Note) { this.ID = ID; this.CatalogName = CatalogName; this.PID = PID; this.DisplayOrder = DisplayOrder; this.Note = Note; } #endregion #region "Public Properties" /// <summary>序号</summary> [Required] [Display(Name = "序号")] public int ID { get; set; } /// <summary>分类名称</summary> [Display(Name = "分类名称")] public string CatalogName { get; set; } /// <summary>父类序号</summary> [Required] [Display(Name = "父类序号")] public int PID { get; set; } /// <summary>显示序号</summary> [Display(Name = "显示序号")] public int DisplayOrder { get; set; } /// <summary>分类说明</summary> [Display(Name = "分类说明")] public string Note { get; set; } #endregion } } #endregion #region "新闻分类信息基础控制类声明" namespace VonPortal.Web.Operators { /// <summary>新闻分类 控制类</summary> public class CatalogCtrl { private CatalogDataProvider dataProvider = null; /// <summary>启动数据库事务</summary> public IDbTransaction BeginTrans() { dataProvider = CatalogDataProvider.CreateProvider(); return dataProvider.DBBeginTrans(); } /// <summary>含数据库事务的构造函数</summary> public CatalogCtrl(IDbTransaction DBTrans) { if (DBTrans == null) dataProvider = CatalogDataProvider.Instance(); else { dataProvider = CatalogDataProvider.CreateProvider(); dataProvider.DBTrans = DBTrans; } } //Read data and write to CatalogInfo class private void setInfoValue(IDataReader reader, CatalogInfo info) { info.ID = reader.GetInt32(0); //序号 info.CatalogName = reader.GetString(1); //分类名称 info.PID = reader.GetInt32(2); //父类序号 info.DisplayOrder = reader.GetInt32(3); //显示序号 info.Note = reader.GetString(4); //分类说明 } /// <summary>检验Catalog信息</summary> public string Check(CatalogInfo info) { string errInfo = ""; return errInfo; } /// <summary>得到本节点的下属节点新的序号</summary> public int NewIdx(int PID) { return dataProvider.GetLasterOrder(PID) + 1; } /// <summary> /// 根据主键 PK_Catalog 提取信息 /// </summary> /// <param name="ID">序号</param> public CatalogInfo GetByCatalog(int ID) { IDataReader reader = dataProvider.GetByCatalog(ID); if (!reader.Read()) { reader.Close(); return null; } CatalogInfo info = new CatalogInfo(); setInfoValue(reader, info); reader.Close(); return info; } /// <summary>得到所有信息</summary> public List<CatalogInfo> List() { List<CatalogInfo> list = new List<CatalogInfo>(); IDataReader reader = dataProvider.List(); while (reader.Read()) { CatalogInfo info = new CatalogInfo(); setInfoValue(reader, info); list.Add(info); } reader.Close(); return list; } /// <summary>根据主键 IDX_Catalog 提取信息</summary> /// <param name="PID">父类序号</param> public List<CatalogInfo> ListByCatalog(int PID) { List<CatalogInfo> list = new List<CatalogInfo>(); IDataReader reader = dataProvider.ListByCatalog(PID); while (reader.Read()) { CatalogInfo info = new CatalogInfo(); setInfoValue(reader, info); list.Add(info); } reader.Close(); return list; } /// <summary>保存Catalog信息</summary> ///<param name="info">信息类</param> public bool Save(CatalogInfo info) { info.ID = dataProvider.Save(info.ID, info.CatalogName, info.PID, info.DisplayOrder, info.Note); return info.ID > 0; } /// <summary>添加Catalog信息</summary> ///<param name="info">信息类</param> public int Add(CatalogInfo info) { info.ID = dataProvider.Add(info.CatalogName, info.PID, info.DisplayOrder, info.Note); return info.ID; } /// <summary>修改Catalog信息</summary> ///<param name="info">信息类</param> public bool Edit(CatalogInfo info) { return dataProvider.Edit(info.ID, info.CatalogName, info.PID, info.DisplayOrder, info.Note) > 0; } /// <summary>根据PK_Catalog删除Catalog信息</summary> /// <param name="ID">序号</param> public int Del(int ID) { return dataProvider.Del(ID); } } } #endregion #region "新闻分类信息操作控制类声明" namespace VonPortal.Web.Tasks { /// <summary>新闻分类 控制类</summary> public class CatalogTask : CatalogCtrl { /// <summary>含数据库事务的构造函数</summary> public CatalogTask(IDbTransaction DBTrans) : base(DBTrans) { } /// <summary> /// 根据主键 PK_Catalog 提取信息 /// </summary> /// <param name="ID">序号</param> public new Task<CatalogInfo> GetByCatalog(int ID) { return Task.Run(() => { return base.GetByCatalog(ID); }); } /// <summary>根据主键 IDX_Catalog 提取信息</summary> /// <param name="PID">父类序号</param> public new Task<List<CatalogInfo>> ListByCatalog(int PID) { return Task.Run(() => { return base.ListByCatalog(PID); }); } /// <summary>保存Catalog信息</summary> ///<param name="info">信息类</param> public new Task<bool> Save(CatalogInfo info) { return Task.Run(() => { return base.Save(info); }); } /// <summary>添加Catalog信息</summary> ///<param name="info">信息类</param> public new Task<int> Add(CatalogInfo info) { return Task.Run(() => { return base.Add(info); }); } /// <summary>修改Catalog信息</summary> ///<param name="info">信息类</param> public new Task<bool> Edit(CatalogInfo info) { return Task.Run(() => { return base.Edit(info); }); } /// <summary>根据PK_Catalog删除Catalog信息</summary> /// <param name="ID">序号</param> public new Task<int> Del(int ID) { return Task.Run(() => { return base.Del(ID); }); } } } #endregion #region "新闻分类信息数据库访问基类声明" namespace VonPortal.Web.Data { /// <summary> /// 数据及操作控制层 /// <seealso cref="VonPortal.Web.Business.CatalogInfo"/> /// <seealso cref="VonPortal.Web.Business.CatalogCtrl"/> /// </summary> public abstract class CatalogDataProvider : DataProvider { #region Shared/Static Methods // singleton reference to the instantiated object private static CatalogDataProvider objProvider = null; /// <summary> /// constructor /// </summary> static CatalogDataProvider() { objProvider = CreateProvider(); } /// <summary> /// dynamically create provider /// </summary> /// <returns>return the provider</returns> public static CatalogDataProvider CreateProvider() { return (CatalogDataProvider)VonPortal.Web.Reflection.CreateDataProvider("von", "NewsModule", "VonPortal.Web.Data.CatalogDataProvider"); } /// <summary> /// The instance of CatalogDataProvider. /// </summary> /// <returns>return the provider</returns> public static CatalogDataProvider Instance() { if (objProvider == null) objProvider = CreateProvider(); return objProvider; } #endregion #region "Catalog Abstract Methods" /// <summary>根据主键 PK_Catalog 提取信息</summary> public abstract IDataReader GetByCatalog(int ID); /// <summary>得到本节点的最后节点序号</summary> public abstract int GetLasterOrder(int PID); /// <summary>根据主键 IDX_Catalog 提取信息</summary> public abstract IDataReader ListByCatalog(int PID); /// <summary>提取全部信息</summary> public abstract IDataReader List(); /// <summary>保存Catalog信息</summary> public abstract int Save(int ID, string CatalogName, int PID, int DisplayOrder, string Note); /// <summary>添加Catalog信息</summary> public abstract int Add(string CatalogName, int PID, int DisplayOrder, string Note); /// <summary>修改Catalog信息</summary> public abstract int Edit(int ID, string CatalogName, int PID, int DisplayOrder, string Note); /// <summary>根据PK_Catalog删除Catalog信息</summary> public abstract int Del(int ID); #endregion } } #endregion
最后的VonPortal.Web.Data.CatalogDataProvider是数据库访问接口类,您可以根据自己的实际需求建立真实的数据库访问类,完成真正的数据库访问,我这里就不在详述了,下面将介绍今天的主角 zTree。大家可以通过 zTree 网上 API 来深入了解其代码编写规则,也可以下载 Demo 来进行研究。
首先按照常规我们先建立后台处理的Model和Controller。
using System.ComponentModel.DataAnnotations; //新闻分类(VON_Catalog) namespace VonPortal.Web.Modules { public class CatalogModel { /// <summary>序号</summary> [Required] [Display(Name = "序号")] public int ID { get; set; } /// <summary>分类名称</summary> [Display(Name = "分类名称")] public string CatalogName { get; set; } /// <summary>父类序号</summary> [Required] [Display(Name = "父类序号")] public int PID { get; set; } /// <summary>显示序号</summary> [Display(Name = "显示序号")] public int DisplayOrder { get; set; } /// <summary>分类说明</summary> [Display(Name = "分类说明")] public string Note { get; set; } } }
using System.Collections.Generic; using System.Web.Mvc; using VonPortal.Web.Models; using VonPortal.Web.Operators; namespace VonPortal.Web.Modules.Controllers { public class NewsController : Controller { public ActionResult NewsCatalogManager() { return PartialView(); } /// <summary> /// ajax 添加一个新闻分类 /// </summary> /// <param name="PID">上级序号</param> /// <param name="CatalogName">分类名称</param> /// <param name="Note">分类说明</param> /// <returns></returns> [HttpPost] public ActionResult NewCatalog(int PID, string CatalogName, string Note) { CatalogCtrl ctrl = new CatalogCtrl(null); CatalogInfo info = new CatalogInfo(0, CatalogName, PID, 0, Note); info.DisplayOrder = ctrl.NewIdx(PID); ctrl.Add(info); return Json(new { bRet = true, sMsg = "添加成功", html = info.ID.ToString() }, "text/html"); } /// <summary> /// ajax 修改一个分类信息 /// </summary> /// <param name="PID">上级序号</param> /// <param name="CatalogName">分类名称</param> /// <param name="Note">分类说明</param> /// <returns></returns> [HttpPost] public ActionResult EditCatalog(int ID, string CatalogName, string Note) { CatalogCtrl ctrl = new CatalogCtrl(null); CatalogInfo info = ctrl.GetByCatalog(ID); info.CatalogName = CatalogName; info.Note = Note; ctrl.Edit(info); return Json(new { bRet = true, sMsg = "修改成功" }, "text/html"); } /// <summary> /// ajax 删除一个新闻分类 /// </summary> /// <param name="ID">分类序号</param> /// <returns></returns> [HttpPost] public ActionResult DeleteCatalog(int ID) { CatalogCtrl ctrl = new CatalogCtrl(null); ctrl.Del(ID); return Json(new { bRet = true, sMsg = "删除成功" }, "text/html"); } /// <summary> /// ajax 加载所有的新闻分类 /// </summary> public void LoadCatalog() { List<CatalogInfo> lst = (new CatalogCtrl(null)).List(); Response.Write("{["); foreach (CatalogInfo info in lst) { Response.Write("{ID:'" + info.ID.ToString() + "'"); Response.Write(",CatalogName:'" + info.CatalogName + "'"); Response.Write(",PID:'" + info.PID.ToString() + "'"); Response.Write(",Note:'" + info.Note + "'},"); } Response.Write("]}"); } } }
这里面我们建立的是一个 PartialView ,以便更好的加载到任何页面中。
我首先简要介绍一下 Controller 的内容;
public ActionResult NewsCatalogManager(); 完成页面的加载
public ActionResult NewCatalog(int PID, string CatalogName, string Note); 支持Ajax实现新分类的添加
public ActionResult EditCatalog(int ID, string CatalogName, string Note); 支持Ajax对现有分类信息的修改
public ActionResult DeleteCatalog(int ID); 支持Ajax删除一个分类节点
public void LoadCatalog();支持Ajax提取全部分类信息;
我们可以看到除了第一个 NewsCatalogManager()是系统加载使用的外,其余的全部采用Ajax的方式完成数据的管理;
下面我们看看也没事如何实现和完成信息调用的吧!
@model IEnumerable<VonPortal.Web.Models.CatalogInfo> @{ Layout = null; } @Html.Import("header", "zTreeStyle_css", @<link rel="stylesheet" href="/content/zTreeStyle.css" type="text / css">) @Html.Import("header", "zTreeDemo_css", @<link rel="stylesheet" href="/content/Demo.css" type="text / css">) @Html.Import("header", "ztree.1.js", @<script type="text/javascript" src="/scripts/jquery.ztree.core.js"></script>) @Html.Import("header", "ztree.2.js", @<script type="text/javascript" src="/scripts/jquery.ztree.excheck.js"></script>) @Html.Import("header", "ztree.3.js", @<script type="text/javascript" src="/scripts/jquery.ztree.exedit.js"></script>) <style type="text/css"> .ztree li span.button.add { margin-left: 2px; margin-right: -1px; background-position: -144px 0; vertical-align: top; *vertical-align: middle; } </style> <div class="content_wrap"> <ul id="tree" class="ztree" style="width:260px; overflow:auto;"></ul> </div> <input id="catalogID" type="hidden" /> <div class="input-group"> <span class="input-group-addon">分类名称</span><input id="catalogName" type="text" class="form-control" placeholder="分类名称" /> </div> <textarea id="catalogNote" type="text" class="form-control" placeholder="分类说明" ></textarea> <button type="button" class="btn btn-info" onclick="addNode($('#catalogName').val(), $('#catalogNote').val())">添加同级</button> <button type="button" class="btn btn-info" onclick="addChild($('#catalogName').val(), $('#catalogNote').val())">添加下级</button> <button type="button" class="btn btn-info" onclick="editNode($('#catalogName').val(), $('#catalogNote').val())">修改本级</button> <script type="text/javascript"> var setting = { view: { dblClickExpand: false, showLine: true, selectedMulti: false, selectedMulti: false }, data: { key: { name: "CatalogName" }, simpleData: { enable: true, idKey: "ID", pIdKey: "PID", rootPId: 0 } }, edit: { enable: true, editNameSelectAll: true, showRemoveBtn: function (treeId, treeNode) { return !treeNode.isParent; }, removeTitle: "删除节点", showRenameBtn: true, renameTitle: "修改节点" }, callback: { beforeRemove: function (treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("tree"); zTree.selectNode(treeNode); return confirm("确认删除 节点 -- " + treeNode.name + " 吗?"); }, onRemove: function (e, treeId, treeNode) { $.ajax({ type: 'POST', url: "News/DeleteCatalog", data: "ID=" + treeNode.ID, dataType: 'json', cache: false, success: function (data) { return true; } }); }, beforeRename: function (e, treeId, treeNode, isCancel) { $.ajax({ type: 'POST', url: "News/EditCatalog", data: { "ID": treeId.ID, "CatalogName": treeNode, "Note": treeId.Note }, dataType: 'json', cache: false, success: function (data) { return true; } }); }, onClick: function (event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("tree"); zTree.selectNode(treeNode); $("#catalogID").val(treeNode.ID); $("#catalogName").val(treeNode.CatalogName); $("#catalogNote").val(treeNode.Note); }, onDrap:function (event, treeId, treeNodes, targetNode, moveType) { alert(treeNodes.length + "," + (targetNode ? (targetNode.tId + ", " + targetNode.name) : "isRoot" )); } } }; $(function () { $.get("News/LoadCatalog", function (response) { var data = eval(response); $.fn.zTree.init($("#tree"), setting, data); }); }); function addNode(name, note) { var zTree = $.fn.zTree.getZTreeObj("tree"); var nodes = zTree.getSelectedNodes(); var pid = 0; if (nodes.length > 0) pid = nodes[0].PID; $.ajax({ type: 'POST', url: "News/NewCatalog", data: { "PID": pid, "CatalogName": $("#catalogName").val(), "Note": $("#catalogNote").val() }, dataType: 'json', cache: false, success: function (data) { if (pid > 0) zTree.addNodes(nodes[0].getParentNode(), -1, { ID: data.html, CatalogName: $("#catalogName").val(), PID: pid, Note: $("#catalogNote").val() }); else zTree.addNodes(null, -1, { ID: data.html, CatalogName: $("#catalogName").val(), PID: pid, Note: $("#catalogNote").val() }); } }); } function addChild(name, note) { var zTree = $.fn.zTree.getZTreeObj("tree"); var nodes = zTree.getSelectedNodes(); if (nodes.length < 1) alert("尚未选中节点"); $.ajax({ type: 'POST', url: "News/NewCatalog", data: { "PID": nodes[0].ID, "CatalogName": $("#catalogName").val(), "Note": $("#catalogNote").val() }, dataType: 'json', cache: false, success: function (data) { zTree.addNodes(nodes[0], -1, { ID: data.html, CatalogName: $("#catalogName").val(), PID: nodes[0].ID, Note: $("#catalogNote").val() }); } }); } function editNode(name, note) { var zTree = $.fn.zTree.getZTreeObj("tree"); var nodes = zTree.getSelectedNodes(); if (nodes.length < 1) alert("尚未选中节点"); $.ajax({ type: 'POST', url: "News/EditCatalog", data: { "ID": nodes[0].ID, "CatalogName": $("#catalogName").val(), "Note": $("#catalogNote").val() }, dataType: 'json', cache: false, success: function (data) { nodes[0].CatalogName = $("#catalogName").val(); nodes[0].Note = $("#catalogNote").val(); zTree.updateNode(nodes[0]); } }); } </script>
首先我们来分析一下 zTree 的配置信息,也就是setting
var setting = { view: { dblClickExpand: false, showLine: true, selectedMulti: false, selectedMulti: false }, data: { key: { name: "CatalogName" }, simpleData: { enable: true, idKey: "ID", pIdKey: "PID", rootPId: 0 } }, edit: { enable: true, editNameSelectAll: true, showRemoveBtn: function (treeId, treeNode) { return !treeNode.isParent; }, removeTitle: "删除节点", showRenameBtn: true, renameTitle: "修改节点" }, callback: { beforeRemove: function (treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("tree"); zTree.selectNode(treeNode); return confirm("确认删除 节点 -- " + treeNode.name + " 吗?"); }, onRemove: function (e, treeId, treeNode) { $.ajax({ type: 'POST', url: "News/DeleteCatalog", data: "ID=" + treeNode.ID, dataType: 'json', cache: false, success: function (data) { return true; } }); }, beforeRename: function (e, treeId, treeNode, isCancel) { $.ajax({ type: 'POST', url: "News/EditCatalog", data: { "ID": treeId.ID, "CatalogName": treeNode, "Note": treeId.Note }, dataType: 'json', cache: false, success: function (data) { return true; } }); }, onClick: function (event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("tree"); zTree.selectNode(treeNode); $("#catalogID").val(treeNode.ID); $("#catalogName").val(treeNode.CatalogName); $("#catalogNote").val(treeNode.Note); }, onDrap:function (event, treeId, treeNodes, targetNode, moveType) { alert(treeNodes.length + "," + (targetNode ? (targetNode.tId + ", " + targetNode.name) : "isRoot" )); } } };
重点是 Data 和 callback,在data里面我们指定了我们CatalogInfo的展示方式,也就是说zTree支持我们自定义数据的展示,我们在这里指定了,我们的展示数据和节点数据的内容:
data: { key: { name: "CatalogName" }, simpleData: { enable: true, idKey: "ID", pIdKey: "PID", rootPId: 0 } },
在callback中我们定义了所有zTree的操作交互内容:我们主要定义了以下几个相应事件:
通过这几个事件,我们就可以实现tree的修改和删除了。
最后为了完成分类信息的添加和全部信息的修改,我们建立了一个信息编辑区域:
<input id="catalogID" type="hidden" /> <div class="input-group"> <span class="input-group-addon">分类名称</span><input id="catalogName" type="text" class="form-control" placeholder="分类名称" /> </div> <textarea id="catalogNote" type="text" class="form-control" placeholder="分类说明" ></textarea> <button type="button" class="btn btn-info" onclick="addNode($('#catalogName').val(), $('#catalogNote').val())">添加同级</button> <button type="button" class="btn btn-info" onclick="addChild($('#catalogName').val(), $('#catalogNote').val())">添加下级</button> <button type="button" class="btn btn-info" onclick="editNode($('#catalogName').val(), $('#catalogNote').val())">修改本级</button>
在这里面我们可以完成信息的同级添加和下级添加,同样,这是通过ajax完成的,对应的javascript函数是
这样我们就完成了zTree的主要基本操作和控制了。
看看我实现的界面效果:
呵呵,还可以吧!