zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。大家可以从zTree 的官网下载和学习。
我这里通过自己的实践,简单介绍一下在.net MVC 环境下,如何实现 ztree 展示和管理树型结构的。
首先我们先假设一下任务,管理新闻的分类,先介绍一下数据结构。
我们还是先建立一个表结构对象和访问控制类
- //新闻分类(VON_Catalog)
- #region"新闻分类信息类声明"
- namespaceVonPortal.Web.Models
- {
- ///<summary>新闻分类信息类</summary>
- publicclassCatalogInfo
- {
- #region"Constructors"
- ///<summary>
- ///构造函数
- ///</summary>
- publicCatalogInfo()
- {
- }
- ///<summary>
- ///含初始化构造函数
- ///</summary>
- ///<paramname="ID">序号</param>
- ///<paramname="CatalogName">分类名称</param>
- ///<paramname="PID">父类序号</param>
- ///<paramname="DisplayOrder">显示序号</param>
- ///<paramname="Note">分类说明</param>
- publicCatalogInfo(intID,stringCatalogName,intPID,intDisplayOrder,stringNote)
- {
- this.ID=ID;
- this.CatalogName=CatalogName;
- this.PID=PID;
- this.DisplayOrder=DisplayOrder;
- this.Note=Note;
- }
- #endregion
- #region"PublicProperties"
- ///<summary>序号</summary>
- [required]
- [Display(Name="序号")]
- publicintID{get;set;}
- ///<summary>分类名称</summary>
- [Display(Name="分类名称")]
- publicstringCatalogName{get;set;}
- ///<summary>父类序号</summary>
- [required]
- [Display(Name="父类序号")]
- publicintPID{get;set;}
- ///<summary>显示序号</summary>
- [Display(Name="显示序号")]
- publicintDisplayOrder{get;set;}
- ///<summary>分类说明</summary>
- [Display(Name="分类说明")]
- publicstringNote{get;set;}
- #endregion
- }
- }
- #endregion
- #region"新闻分类信息基础控制类声明"
- namespaceVonPortal.Web.Operators
- {
- ///<summary>新闻分类控制类</summary>
- publicclassCatalogCtrl
- {
- privateCatalogDataProviderdataProvider=null;
- ///<summary>启动数据库事务</summary>
- publicIDbTransactionBeginTrans()
- {
- dataProvider=CatalogDataProvider.CreateProvider();
- returndataProvider.DBBeginTrans();
- }
- ///<summary>含数据库事务的构造函数</summary>
- publicCatalogCtrl(IDbTransactionDBTrans)
- {
- if(DBTrans==null)
- dataProvider=CatalogDataProvider.Instance();
- else
- {
- dataProvider=CatalogDataProvider.CreateProvider();
- dataProvider.DBTrans=DBTrans;
- }
- }
- //ReaddataandwritetoCatalogInfoclass
- privatevoidsetInfoValue(IDataReaderreader,CatalogInfoinfo)
- {
- 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>
- publicstringCheck(CatalogInfoinfo)
- {
- stringerrInfo="";
- returnerrInfo;
- }
- ///<summary>得到本节点的下属节点新的序号</summary>
- publicintNewIdx(intPID)
- {
- returndataProvider.GetLasterOrder(PID)+1;
- }
- ///<summary>
- ///根据主键PK_Catalog提取信息
- ///</summary>
- ///<paramname="ID">序号</param>
- publicCatalogInfoGetByCatalog(intID)
- {
- IDataReaderreader=dataProvider.GetByCatalog(ID);
- if(!reader.Read())
- {
- reader.Close();
- returnnull;
- }
- CatalogInfoinfo=newCatalogInfo();
- setInfoValue(reader,info);
- reader.Close();
- returninfo;
- }
- ///<summary>得到所有信息</summary>
- publicList<CatalogInfo>List()
- {
- List<CatalogInfo>list=newList<CatalogInfo>();
- IDataReaderreader=dataProvider.List();
- while(reader.Read())
- {
- CatalogInfoinfo=newCatalogInfo();
- setInfoValue(reader,info);
- list.Add(info);
- }
- reader.Close();
- returnlist;
- }
- ///<summary>根据主键IDX_Catalog提取信息</summary>
- ///<paramname="PID">父类序号</param>
- publicList<CatalogInfo>ListByCatalog(intPID)
- {
- List<CatalogInfo>list=newList<CatalogInfo>();
- IDataReaderreader=dataProvider.ListByCatalog(PID);
- while(reader.Read())
- {
- CatalogInfoinfo=newCatalogInfo();
- setInfoValue(reader,info);
- list.Add(info);
- }
- reader.Close();
- returnlist;
- }
- ///<summary>保存Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicboolSave(CatalogInfoinfo)
- {
- info.ID=dataProvider.Save(info.ID,info.CatalogName,info.PID,info.DisplayOrder,info.Note);
- returninfo.ID>0;
- }
- ///<summary>添加Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicintAdd(CatalogInfoinfo)
- {
- info.ID=dataProvider.Add(info.CatalogName,info.Note);
- returninfo.ID;
- }
- ///<summary>修改Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicboolEdit(CatalogInfoinfo)
- {
- returndataProvider.Edit(info.ID,info.Note)>0;
- }
- ///<summary>根据PK_Catalog删除Catalog信息</summary>
- ///<paramname="ID">序号</param>
- publicintDel(intID)
- {
- returndataProvider.Del(ID);
- }
- }
- }
- #endregion
- #region"新闻分类信息操作控制类声明"
- namespaceVonPortal.Web.Tasks
- {
- ///<summary>新闻分类控制类</summary>
- publicclassCatalogTask:CatalogCtrl
- {
- ///<summary>含数据库事务的构造函数</summary>
- publicCatalogTask(IDbTransactionDBTrans):base(DBTrans)
- {
- }
- ///<summary>
- ///根据主键PK_Catalog提取信息
- ///</summary>
- ///<paramname="ID">序号</param>
- publicnewTask<CatalogInfo>GetByCatalog(intID)
- {
- returnTask.Run(()=>
- {
- returnbase.GetByCatalog(ID);
- });
- }
- ///<summary>根据主键IDX_Catalog提取信息</summary>
- ///<paramname="PID">父类序号</param>
- publicnewTask<List<CatalogInfo>>ListByCatalog(intPID)
- {
- returnTask.Run(()=>
- {
- returnbase.ListByCatalog(PID);
- });
- }
- ///<summary>保存Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicnewTask<bool>Save(CatalogInfoinfo)
- {
- returnTask.Run(()=>
- {
- returnbase.Save(info);
- });
- }
- ///<summary>添加Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicnewTask<int>Add(CatalogInfoinfo)
- {
- returnTask.Run(()=>
- {
- returnbase.Add(info);
- });
- }
- ///<summary>修改Catalog信息</summary>
- ///<paramname="info">信息类</param>
- publicnewTask<bool>Edit(CatalogInfoinfo)
- {
- returnTask.Run(()=>
- {
- returnbase.Edit(info);
- });
- }
- ///<summary>根据PK_Catalog删除Catalog信息</summary>
- ///<paramname="ID">序号</param>
- publicnewTask<int>Del(intID)
- {
- returnTask.Run(()=>
- {
- returnbase.Del(ID);
- });
- }
- }
- }
- #endregion
- #region"新闻分类信息数据库访问基类声明"
- namespaceVonPortal.Web.Data
- {
- ///<summary>
- ///数据及操作控制层
- ///<seealsocref="VonPortal.Web.Business.CatalogInfo"/>
- ///<seealsocref="VonPortal.Web.Business.CatalogCtrl"/>
- ///</summary>
- publicabstractclassCatalogDataProvider:DataProvider
- {
- #regionShared/StaticMethods
- //singletonreferencetotheinstantiatedobject
- privatestaticCatalogDataProviderobjProvider=null;
- ///<summary>
- ///constructor
- ///</summary>
- staticCatalogDataProvider()
- {
- objProvider=CreateProvider();
- }
- ///<summary>
- ///dynamicallycreateprovider
- ///</summary>
- ///<returns>returntheprovider</returns>
- publicstaticCatalogDataProviderCreateProvider()
- {
- return(CatalogDataProvider)VonPortal.Web.Reflection.CreateDataProvider("von","NewsModule","VonPortal.Web.Data.CatalogDataProvider");
- }
- ///<summary>
- ///TheinstanceofCatalogDataProvider.
- ///</summary>
- ///<returns>returntheprovider</returns>
- publicstaticCatalogDataProviderInstance()
- {
- if(objProvider==null)objProvider=CreateProvider();
- returnobjProvider;
- }
- #endregion
- #region"CatalogAbstractMethods"
- ///<summary>根据主键PK_Catalog提取信息</summary>
- publicabstractIDataReaderGetByCatalog(intID);
- ///<summary>得到本节点的最后节点序号</summary>
- publicabstractintGetLasterOrder(intPID);
- ///<summary>根据主键IDX_Catalog提取信息</summary>
- publicabstractIDataReaderListByCatalog(intPID);
- ///<summary>提取全部信息</summary>
- publicabstractIDataReaderList();
- ///<summary>保存Catalog信息</summary>
- publicabstractintSave(intID,stringNote);
- ///<summary>添加Catalog信息</summary>
- publicabstractintAdd(stringCatalogName,stringNote);
- ///<summary>修改Catalog信息</summary>
- publicabstractintEdit(intID,stringNote);
- ///<summary>根据PK_Catalog删除Catalog信息</summary>
- publicabstractintDel(intID);
- #endregion
- }
- }
- #endregion
最后的VonPortal.Web.Data.CatalogDataProvider是数据库访问接口类,您可以根据自己的实际需求建立真实的数据库访问类,完成真正的数据库访问,我这里就不在详述了,下面将介绍今天的主角 zTree。大家可以通过 zTree 网上 API 来深入了解其代码编写规则,也可以下载Demo来进行研究。
首先按照常规我们先建立后台处理的Model和Controller。
- usingSystem.ComponentModel.DataAnnotations;
- //新闻分类(VON_Catalog)
- namespaceVonPortal.Web.Modules
- {
- publicclassCatalogModel
- {
- ///<summary>序号</summary>
- [required]
- [Display(Name="序号")]
- publicintID{get;set;}
- ///<summary>分类名称</summary>
- [Display(Name="分类名称")]
- publicstringCatalogName{get;set;}
- ///<summary>父类序号</summary>
- [required]
- [Display(Name="父类序号")]
- publicintPID{get;set;}
- ///<summary>显示序号</summary>
- [Display(Name="显示序号")]
- publicintDisplayOrder{get;set;}
- ///<summary>分类说明</summary>
- [Display(Name="分类说明")]
- publicstringNote{get;set;}
- }
- }
- usingSystem.Collections.Generic;
- usingSystem.Web.Mvc;
- usingVonPortal.Web.Models;
- usingVonPortal.Web.Operators;
- namespaceVonPortal.Web.Modules.Controllers
- {
- publicclassNewsController:Controller
- {
- publicActionResultNewsCatalogManager()
- {
- returnPartialView();
- }
- ///<summary>
- ///ajax添加一个新闻分类
- ///</summary>
- ///<paramname="PID">上级序号</param>
- ///<paramname="CatalogName">分类名称</param>
- ///<paramname="Note">分类说明</param>
- ///<returns></returns>
- [HttpPost]
- publicActionResultNewCatalog(intPID,stringNote)
- {
- CatalogCtrlctrl=newCatalogCtrl(null);
- CatalogInfoinfo=newCatalogInfo(0,CatalogName,PID,Note);
- info.DisplayOrder=ctrl.NewIdx(PID);
- ctrl.Add(info);
- returnJson(new{bRet=true,sMsg="添加成功",html=info.ID.ToString()},"text/html");
- }
- ///<summary>
- ///ajax修改一个分类信息
- ///</summary>
- ///<paramname="PID">上级序号</param>
- ///<paramname="CatalogName">分类名称</param>
- ///<paramname="Note">分类说明</param>
- ///<returns></returns>
- [HttpPost]
- publicActionResultEditCatalog(intID,stringNote)
- {
- CatalogCtrlctrl=newCatalogCtrl(null);
- CatalogInfoinfo=ctrl.GetByCatalog(ID);
- info.CatalogName=CatalogName;
- info.Note=Note;
- ctrl.Edit(info);
- returnJson(new{bRet=true,sMsg="修改成功"},"text/html");
- }
- ///<summary>
- ///ajax删除一个新闻分类
- ///</summary>
- ///<paramname="ID">分类序号</param>
- ///<returns></returns>
- [HttpPost]
- publicActionResultDeleteCatalog(intID)
- {
- CatalogCtrlctrl=newCatalogCtrl(null);
- ctrl.Del(ID);
- returnJson(new{bRet=true,sMsg="删除成功"},"text/html");
- }
- ///<summary>
- ///ajax加载所有的新闻分类
- ///</summary>
- publicvoidLoadCatalog()
- {
- List<CatalogInfo>lst=(newCatalogCtrl(null)).List();
- Response.Write("{[");
- foreach(CatalogInfoinfoinlst)
- {
- 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 Note); 支持Ajax对现有分类信息的修改
public ActionResult DeleteCatalog(int ID); 支持Ajax删除一个分类节点
public void LoadCatalog();支持Ajax提取全部分类信息;
我们可以看到除了第一个NewsCatalogManager()是系统加载使用的外,其余的全部采用Ajax的方式完成数据的管理;
下面我们看看也没事如何实现和完成信息调用的吧!
- @modelIEnumerable<VonPortal.Web.Models.CatalogInfo>
- @{
- Layout=null;
- }
- @Html.Import("header","zTreeStyle_css",@<linkrel="stylesheet"href="/content/zTreeStyle.css"type="text/css">)
- @Html.Import("header","zTreeDemo_css",@<linkrel="stylesheet"href="/content/Demo.css"type="text/css">)
- @Html.Import("header","ztree.1.js",@<scripttype="text/javascript"src="/scripts/jquery.ztree.core.js"></script>)
- @Html.Import("header","ztree.2.js",@<scripttype="text/javascript"src="/scripts/jquery.ztree.excheck.js"></script>)
- @Html.Import("header","ztree.3.js",@<scripttype="text/javascript"src="/scripts/jquery.ztree.exedit.js"></script>)
- <styletype="text/css">
- .ztreelispan.button.add{
- margin-left:2px;
- margin-right:-1px;
- background-position:-144px0;
- vertical-align:top;
- *vertical-align:middle;
- }
- </style>
- <divclass="content_wrap">
- <ulid="tree"class="ztree"style="width:260px;overflow:auto;"></ul>
- </div>
- <inputid="catalogID"type="hidden"/>
- <divclass="input-group">
- <spanclass="input-group-addon">分类名称</span><inputid="catalogName"type="text"class="form-control"placeholder="分类名称"/>
- </div>
- <textareaid="catalogNote"type="text"class="form-control"placeholder="分类说明"></textarea>
- <buttontype="button"class="btnbtn-info"onclick="addNode($('#catalogName').val(),$('#catalogNote').val())">添加同级</button>
- <buttontype="button"class="btnbtn-info"onclick="addChild($('#catalogName').val(),$('#catalogNote').val())">添加下级</button>
- <buttontype="button"class="btnbtn-info"onclick="editNode($('#catalogName').val(),$('#catalogNote').val())">修改本级</button>
- <scripttype="text/javascript">
- varsetting={
- 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){
- varzTree=$.fn.zTree.getZTreeObj("tree");
- zTree.selectNode(treeNode);
- returnconfirm("确认删除节点--"+treeNode.name+"吗?");
- },onRemove:function(e,treeId,treeNode){
- $.ajax({
- type:'POST',url:"News/DeleteCatalog",data:"ID="+treeNode.ID,dataType:'json',cache:false,success:function(data){returntrue;}
- });
- },beforeRename:function(e,treeNode,isCancel){
- $.ajax({
- type:'POST',url:"News/EditCatalog",data:{"ID":treeId.ID,"CatalogName":treeNode,"Note":treeId.Note},onClick:function(event,treeNode){
- varzTree=$.fn.zTree.getZTreeObj("tree");
- zTree.selectNode(treeNode);
- $("#catalogID").val(treeNode.ID);
- $("#catalogName").val(treeNode.CatalogName);
- $("#catalogNote").val(treeNode.Note);
- },onDrap:function(event,treeNodes,targetNode,moveType){
- alert(treeNodes.length+","+(targetNode?(targetNode.tId+","+targetNode.name):"isRoot"));
- }
- }
- };
- $(function(){
- $.get("News/LoadCatalog",function(response){
- vardata=eval(response);
- $.fn.zTree.init($("#tree"),setting,data);
- });
- });
- functionaddNode(name,note)
- {
- varzTree=$.fn.zTree.getZTreeObj("tree");
- varnodes=zTree.getSelectedNodes();
- varpid=0;
- if(nodes.length>0)pid=nodes[0].PID;
- $.ajax({
- type:'POST',url:"News/NewCatalog",data:{"PID":pid,"CatalogName":$("#catalogName").val(),"Note":$("#catalogNote").val()},success:function(data){
- if(pid>0)zTree.addNodes(nodes[0].getParentNode(),-1,{ID:data.html,CatalogName:$("#catalogName").val(),PID:pid,Note:$("#catalogNote").val()});
- elsezTree.addNodes(null,Note:$("#catalogNote").val()});
- }
- });
- }
- functionaddChild(name,note){
- varzTree=$.fn.zTree.getZTreeObj("tree");
- varnodes=zTree.getSelectedNodes();
- if(nodes.length<1)alert("尚未选中节点");
- $.ajax({
- type:'POST',data:{"PID":nodes[0].ID,success:function(data){
- zTree.addNodes(nodes[0],PID:nodes[0].ID,Note:$("#catalogNote").val()});
- }
- });
- }
- functioneditNode(name,data:{"ID":nodes[0].ID,success:function(data){
- nodes[0].CatalogName=$("#catalogName").val();
- nodes[0].Note=$("#catalogNote").val();
- zTree.updateNode(nodes[0]);
- }
- });
- }
- </script>
首先我们来分析一下 zTree 的配置信息,也就是setting
- varsetting={
- view:{dblClickExpand:false,"+targetNode.name):"isRoot"));
- }
- }
- };
重点是 Data 和 callback,在data里面我们指定了我们CatalogInfo的展示方式,也就是说zTree支持我们自定义数据的展示,我们在这里指定了,我们的展示数据和节点数据的内容:
data: { key: { name: "CatalogName" },simpleData: { enable: true,idKey: "ID",pIdKey: "PID",rootPId: 0 } },
在callback中我们定义了所有zTree的操作交互内容:我们主要定义了以下几个相应事件:
最后为了完成分类信息的添加和全部信息的修改,我们建立了一个信息编辑区域:
- <inputid="catalogID"type="hidden"/>
- <divclass="input-group">
- <spanclass="input-group-addon">分类名称</span><inputid="catalogName"type="text"class="form-control"placeholder="分类名称"/>
- </div>
- <textareaid="catalogNote"type="text"class="form-control"placeholder="分类说明"></textarea>
- <buttontype="button"class="btnbtn-info"onclick="addNode($('#catalogName').val(),$('#catalogNote').val())">修改本级</button>
在这里面我们可以完成信息的同级添加和下级添加,同样,这是通过ajax完成的,对应的javascript函数是
这样我们就完成了zTree的主要基本操作和控制了。
看看我实现的界面效果:
呵呵,还可以吧!