使用 Kendo 进行带拖放的动态 Treeview






4.80/5 (52投票s)
本文解释了如何为 Kendo TreeView 创建数据源,特别是针对需要嵌套查询的组织结构图,例如人员或文档。当拖放节点到另一个节点后,会进行保存。请查看演示。
引言
动态树状视图(Dynamic treeview)配合拖放功能,在需要根据特定嵌套层级来排列员工或文档时非常有用。假设组织中的某位员工需要向其上级经理汇报任务,而这位经理又有一位更高级别的经理,依此类推。在另一个例子中,您想整理文档并定义它们的父级,例如 { 财务 --> 薪资 --> 个人 } 或 { 办公室 --> 信件 --> 进口 }。Kendoui TreeView 具有拖放功能,但他们的示例中使用的是 JSON 格式的静态字符串(static string),例如 [{ id: "1", text: "P1", items: [{ id: "5", text: "P2"}] }]
,您需要将这个字符串传递给 Kendo TreeView 的数据源。我编写了一个方法来生成这些 JSON,以便传递给数据源,并在您更改节点位置(父级)后进行保存。
背景
首先,您需要从 kendoui treeview
添加一些 CSS 和 JS 文件到您的项目中。
有关 Kendo UI treeview
的更多信息,请参阅以下链接,了解如何绑定本地数据及其结构。
使用此代码
下面简要介绍了我如何创建一个方法来生成 JSON 格式。
步骤 1:数据库
首先,创建一个数据库,然后创建一个名为 personal 的表。
步骤 1.1:表
然后填写这个表。
步骤 2:数据模型
然后创建实体数据模型(快速且简单)。
步骤 3:Model 文件夹内的 Model 类
这段代码是为 MVC 编写的,因此下面的部分位于 Model 文件夹中,从表中获取数据。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Treeview.Models
{
public class Personal
{
public int ID { get; set; }
public int Parent_ID { get; set; }
public string Name { get; set; }
}
}
步骤 4:Controller 文件夹内的 Main 方法
我在 controllers 文件夹中创建了一个名为 "PersonalController.cs" 的文件,内容如下:
实际上,下面的逻辑描述将生成自引用的表(self reference table)并以 JSON 格式输出,然后将其内容传递给 treeview
数据源。
我为步骤 3 中的数据模型生成了这个方法,它说明了组织中可能存在有经理或雇主的人员。如果您的数据库不同,您应该查看代码并稍作修改。
要生成分层数据源(hierarchical Data Source)为 JSON 文件,请遵循以下步骤,并参阅链接以获取完整说明。
步骤 1
创建一个名为 Treeview
的**嵌套**方法。
public string Treeview(int itemID, string mystr, int j, int flag)
该方法的输出是 JSON 格式的字符串(string),并接收:
itemID
--> 当前节点的 ID。mystr
--> 前一个 JSON 字符串。- { 每次递归调用此方法时,新的字符串都会添加到
mystr
中 } j
是内部计数器。flag
表示当前节点是否有子节点。
第二步
第一次调用此方法时,可以从 MVC 的 Action 或应用程序的其他部分调用,例如 Treeview(0,””,0,0)
。
- 假设您不知道当前节点,那么
itemid
就等于零。 - 第一次调用时,您还没有任何 JSON 字符串。
j = 0
,同理。flag = 0
,同理。
步骤 3
检查当前节点是否有父节点?
- 主节点根:如果您是主节点,只需进入此方法,假设您需要
- 从数据库中选择。
- 没有父节点且其父 ID 为
NULL
的节点。 - 在此,生成您的 JSON 字符串,例如
**mystr = "["**
。
- 嵌套节点:如果此方法被调用了一次以上,请检查所有节点。
- 其父级等于
itemid
的节点。 - 在此,生成您的 JSON 字符串,例如
**mystr = ",item:["**
。
- 其父级等于
步骤 4
现在您拥有了从第三步获取的数据列表。
- 创建一个
foreach
循环并调用每个节点,然后按如下方式写入: foreach
(item inquerylist
)**mystr = { id: “” , text: “”**
- 在此循环中,检查该节点是否有子节点?
Querylist= select personal where reportsto=item.id
- **(它有子节点)** --> 再次调用
Treeview
方法,例如:mystr = Treeview (item.id, mystr, i,1)
此时,您的
item.id
是当前节点,mystr
是已生成的所有字符串。到目前为止,
i
对应j
,flag
等于一,表示此节点是父节点且有子节点。 - **(它没有子节点 && 此节点不是最后一个节点)**
**mystr =" }, "**
- **(它没有子节点 && 此节点是最后一个节点)**
- 计算此节点的父节点数量。
Foreach parent put **mystr = "}]"**
- 计算此节点父节点的子节点数量。
if (子节点数 = 0) **mystr = "}]"**
if (子节点数 > 0) **mystr = "}]"**
- if (此节点是最后一个子节点 && 此节点的父节点是最后一个父节点)
**mystr = "},"**
- if (此节点是最后一个子节点 && 此节点的父节点是最后一个父节点 &&
flag=1 ) **mystr =" },"**
- if (此节点是最后一个子节点 && 此节点的父节点是最后一个父节点 && flag=0 )
**mystr =" }]"**
- if (此节点是最后一个子节点 && 此节点的父节点是最后一个父节点)
- 计算此节点的父节点数量。
- **(它有子节点)** --> 再次调用
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using System.Diagnostics;
using System.Text;
namespace Treeview.Controllers
{
public class PersonalController : Controller
{
int mainNode = 0;
int childquantity = 0;
int myflag;
[HttpPost]
public ActionResult SaveNode(string childid, string parentid)
{
//*****REVISION******Do not allow reportsto = itself
if (childid != null && parentid != null && parentid != childid )
{
string source = childid;
string destination = parentid;
using (var ctx = new TreeviewEntities())
{
var personals = ctx.Personals.Where(m => m.Name == source).First();
var personalsParent = ctx.Personals.Where(m => m.Name == destination).First();
//***check
//*****REVISION******Do not allow make circle in data base
//such as P1 is parent for P2 and also P2 is parent for
//P1 ==> Prevent these mistake by checking it
//
if (personalsParent.ReportsTo!=personals.ID)
{
personals.ReportsTo = Convert.ToInt16(personalsParent.ID);
ctx.SaveChanges();
}
//***check
}
}
return RedirectToAction("Index");
}
public string Treeview(int itemID, string mystr, int j, int flag)
{
List<Personal> querylist = new List<Personal>();
var ctx = new TreeviewEntities();
if (flag==0)
{
querylist = (from m in ctx.Personals
where m.ReportsTo == null
select m).ToList();
mainNode = querylist.Count;
mystr += "[";
}
if (flag == 1)
{
querylist = (from m in ctx.Personals
where m.ReportsTo == itemID
select m).ToList();
mainNode = querylist.Count;
mystr += ",items:[";
}
//Below line shows an example of how to make parent node with his child
//[{ id: "1", text: "P1", items: [{ id: "5", text: "P2" }] }]
int i=1;
foreach (var item in querylist)
{
myflag = 0;
mystr += "{id:\"" + item.ID +
"\",text:\"" + item.Name + "\"";
List<Personal> querylistParent = new List<Personal>();
//Check this parent has child or not , if yes how many?
querylistParent = (from m in ctx.Personals
where m.ReportsTo == item.ID
select m).ToList();
childquantity = querylistParent.Count;
//If Parent Has Child again call Treeview with new parameter
if (childquantity > 0)
{
mystr = Treeview(item.ID, mystr, i, 1);
}
//No Child and No Last Node
else if (childquantity == 0 && i < querylist.Count)
{
mystr += "},";
}
//No Child and Last Node
else if (childquantity == 0 && i == querylist.Count)
{
int fcheck2=0;
int fcheck3 = 0;
int counter = 0;
int flagbreak = 0;
int currentparent;
List<Personal> parentquery ;
List<Personal> childlistquery;
TempData["counter"] =0;
currentparent = Convert.ToInt16(item.ReportsTo);
int coun;
while (currentparent != 0)
{
//count parent of parent
fcheck2 = 0;
fcheck3 = 0;
parentquery = new List<Personal>();
parentquery = (from m in ctx.Personals
where m.ID == currentparent
select m).ToList();
var rep2 = (from h in parentquery
select new { h.ReportsTo }).First();
//put {[ up to end
//list of child
childlistquery = new List<Personal>();
childlistquery = (from m in ctx.Personals
where m.ReportsTo == currentparent
select m).ToList();
foreach (var item22 in childlistquery)
{
if (mystr.Contains(item22.ID.ToString()))
{
if (item22.ReportsTo == currentparent)
{
fcheck3 += 1;
if (fcheck3 == 1)
{
counter += 1;
}
}
}
else
{
myflag = 1;
if (item22.ReportsTo == currentparent)
{
fcheck2+=1;
if (fcheck2==1)
{
counter -= 1;
flagbreak = 1;
}
}
}
}
var result55 =
(from h in parentquery select new { h.ID }).First();
coun = Convert.ToInt16(result55.ID);
TempData["coun"] = Convert.ToInt16(coun);
currentparent = Convert.ToInt16(rep2.ReportsTo);
if (flagbreak == 1)
{
break;
}
}
for (int i2 = 0; i2 < counter; i2++)
{
mystr += "}]";
}
List<Personal> lastchild = new List<Personal>();
lastchild = (from m in ctx.Personals
where m.ReportsTo == item.ReportsTo
select m).ToList();
List<Personal> lastparent = new List<Personal>();
lastparent = (from m in ctx.Personals
where m.ReportsTo == null
select m).ToList();
if (lastchild.Count > 0)
{
var result_lastchild =
(from h in lastchild select new { h.ID }).Last();
var result_lastparent =
(from h in lastparent select new { h.ID }).Last();
int mycount = Convert.ToInt16(TempData["coun"]);
if (item.ID == result_lastchild.ID &&
mycount == result_lastparent.ID && myflag == 0)
{
mystr += "}]";
}
else if (item.ID == result_lastchild.ID &&
mycount == result_lastparent.ID && myflag == 1)
{
mystr += "},";
}
else if (item.ID == result_lastchild.ID &&
mycount != result_lastparent.ID)
{
mystr += "},";
}
}
// finish }]
else if (lastchild.Count == 0 && item.ReportsTo == null)
{
mystr += "}]";
}
}
i++;
}
return mystr;
}
public ActionResult Index()
{
ViewData["treeviewds"] = Treeview(0, "", 0, 0);
return View();
}
}
}
步骤 5:UI (视图)
然后,我在 Views --> Personal 文件夹中创建了一个名为 "Index.cshtml" 的文件,内容如下:
步骤 5.1:Head (头部)
在 head
部分,您应该定义 CSS 和 JS 文件。
步骤 5.2:Body (主体)
然后创建一个 treeview div
用于追加 JSON 字符串,以及 tvformat
用于传递 viewdata["treeviewds"]
。
步骤 5.3:Script (脚本)
然后编写脚本以从数据库获取 treeview
数据,并 POST (保存) 更改了父节点(位置)的节点。
步骤 5.4:CSS
然后为此创建样式。
历史
2014 年 7 月 30 日
我已将演示链接添加到本文中。
2014 年 8 月 1 日
我已在本文中修正了两个 bug:第一个是如果您点击一个节点然后将其拖放到自身,在 SaveNode
Action 中,我已经编辑了代码,如果 parentid == childid
,则不执行任何操作。
第二个是您无法在 treeview
中创建循环,例如 P1
是 P2
的父节点,而 P2
也是 P1
的父节点,这是不正确的,会造成循环。
反馈
请随时对本文提供任何反馈;很高兴看到您的评论和投票。如果您有任何问题,请随时在此处提问。