利用 zTree 在 MVC 下实现树型结构管理

zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。大家可以从 zTree 的官网下载和学习。

我这里通过自己的实践,简单介绍一下在.net MVC 环境下,如何实现 ztree 展示和管理树型结构的。

首先我们先假设一下任务,管理新闻的分类,先介绍一下数据结构。

利用 zTree 在 MVC 下实现树型结构管理_第1张图片

我们还是先建立一个表结构对象和访问控制类

//新闻分类(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的操作交互内容:我们主要定义了以下几个相应事件:

利用 zTree 在 MVC 下实现树型结构管理_第2张图片

通过这几个事件,我们就可以实现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的主要基本操作和控制了。

看看我实现的界面效果:

利用 zTree 在 MVC 下实现树型结构管理_第3张图片

呵呵,还可以吧!

你可能感兴趣的:(ztree,Ajax,mvc)