[英]Kendo UI Grid multi level hierarchy (n-levels of hierarchy)
I am using the Kendo UI Grid and currently display parent child records appropriately. 我正在使用Kendo UI Grid并且当前正确显示父子记录。 However, it turns out that i will actually need to display n-levels vs. strictly parent-child.
然而,事实证明我实际上需要显示n级与严格的父级。 Not every record will have children, but some will have multiple levels.
并非每条记录都有孩子,但有些会有多个级别。
Current grid code: 当前网格代码:
var jgrid = $("#boxesgrid").kendoGrid({
columns: [
{
field: "JobId",
hidden: true
},
{
field: "PercentComplete",
hidden: true
},
{
field: "JobStatusId",
hidden: true
},
{
field: "AppName",
title: "App",
template: "<span>${AppName}</span><img class='health-img-r' id=app-${JobId} title='health' src='' alt='health_png' />",
width: "5%",
editable: false,
sortable: false
},
{
field: "JobName",
title: "Box Name",
width: "17%",
filterable: false
},
{
field: "StartTime",
title: "Start Time",
width: "14%",
filterable: false
},
{
field: "EndTime",
title: "End Time",
width: "14%",
filterable: false
},
{
field: "JobStatusId",
title: "Status",
template: "<img class='health-img-l' id=app-${JobId} title='health' src='' alt='health_png' /><span>${JobStatus}</span>",
editable: false,
filterable: false
}
],
sortable: {
mode: "single",
allowUnsort: true
},
pageable: {
pageSizes: [50],
numeric: true,
refresh: true,
pageSize: 50
},
autoBind: false,
scrollable: false,
resizable: true,
detailInit: detailInit,
dataSource: boxesDataSource,
dataBound: function () {
var grid = this;
grid.tbody.find(">tr").each(function () {
var row = $(this).closest("tr");
var model = grid.dataItem(row);
var img = $(this).find("img");
if (model.JobStatusId == 4 && model.PercentComplete < 100) {
img.attr("src", function() {
return imgSrc + imgGreen;
});
} else if (model.JobStatusId == 4) {
img.attr("src", function() {
return imgSrc + imgAmber;
});
} else if (model.JobStatusId == 7) {
img.attr("src", function() {
return imgSrc + imgIce;
});
} else if (model.JobStatusId == 8) {
img.attr("src", function() {
return imgSrc + imgHold;
});
} else if (model.JobStatusId == 5) {
img.attr("src", function() {
return imgSrc + imgBlue;
});
} else {
img.attr("src", function() {
return imgSrc + imgRed;
});
}
});
}
}).data("kendoGrid");
Current child grid: 当前子网格:
function detailInit(e) {
$("<div/>").appendTo(e.detailCell).kendoGrid({
dataSource: {
transport: {
read: {
url: "/api/BoxJobs"
},
parameterMap: function (data) {
data.parentid = e.data.JobId;
data.appid = e.data.AppId;
return kendo.stringify(data);
}
},
schema: {
model: { id: "JobId" }
},
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
scrollable: false,
sortable: true,
columns: [
{
field: "JobId",
hidden: true
},
{
field: "PercentComplete",
hidden: true
},
{
field: "JobStatusId",
hidden: true
},
{
field: "JobName",
title: "Job Name",
template: "<span>${JobName}</span><img class='health-img-l' id=app-${JobId} title='health' src='' alt='health_png' />",
width: "23%",
filterable: false,
sortable: false
},
{
field: "StartTime",
title: "Start Time",
width: "10%",
editable: false,
filterable: false,
sortable: false
},
{
field: "EndTime",
title: "End Time",
width: "10%",
editable: false,
filterable: false,
sortable: false
},
{
field: "ElapsedTime",
title: "Elapsed</br>Time",
width: "4%",
editable: false,
filterable: false,
sortable: false
},
{
field: "MeanRunTime",
title: "Mean Run</br>Time",
width: "3.5%",
editable: false,
filterable: false,
sortable: false
},
{
field: "PredecessorJobName",
title: "Previous Job",
width: "17%",
filterable: false,
sortable: false
},
{
field: "JobStatusId",
title: "Status",
template: "<img class='health-img-l' id=app-${JobId} title='health' src='' alt='health_png' /><span>${JobStatus}</span>",
editable: false,
filterable: false,
sortable: false
}
],
dataBound: function () {
var grid = this;
grid.tbody.find(">tr").each(function () {
var row = $(this).closest("tr");
var model = grid.dataItem(row);
var img = $(this).find("img");
if (model.JobStatusId == 4 && model.PercentComplete < 100) {
img.attr("src", function() {
return imgSrc + imgGreen;
});
} else if (model.JobStatusId == 4) {
img.attr("src", function() {
return imgSrc + imgAmber;
});
} else if (model.JobStatusId == 7) {
img.attr("src", function() {
return imgSrc + imgIce;
});
} else if (model.JobStatusId == 8) {
img.attr("src", function() {
return imgSrc + imgHold;
});
} else if (model.JobStatusId == 5) {
img.attr("src", function() {
return imgSrc + imgBlue;
});
} else {
img.attr("src", function() {
return imgSrc + imgRed;
});
}
});
}
});
}
Sample top level data: 顶级数据示例:
{"Total":638,
"Data":[
{"JobId":1,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL_BOX","JobType":"box","TimeZone":"Chicago (Central Standard Time)","ParentJobName":null,"ParentJobId":null,"PredecessorJobName":null,"PredecessorJobId":null,"StartTime":"6/2/2014 5:00:02 PM","EndTime":"","ElapsedTime":"00:58:31","JobStatusId":4,"JobStatus":"Running","MeanRunTime":"06:57:04","PercentComplete":14.00,"TotalCount":638.0,"Children":3}
]
}
Sample 2nd level data: 第二级数据样本:
[
{"JobId":63,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL2_BOX","JobType":"box","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL_BOX","ParentJobId":1,"PredecessorJobName":null,"PredecessorJobId":null,"StartTime":"6/2/2014 5:00:06 PM","EndTime":"","ElapsedTime":"00:58:27","JobStatusId":4,"JobStatus":"Running","MeanRunTime":"06:57:00","PercentComplete":14.00,"TotalCount":0.0,"Children":3},
{"JobId":64,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL1_BOX","JobType":"box","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL_BOX","ParentJobId":1,"PredecessorJobName":null,"PredecessorJobId":null,"StartTime":"6/2/2014 5:00:06 PM","EndTime":"","ElapsedTime":"00:58:27","JobStatusId":4,"JobStatus":"Running","MeanRunTime":"01:42:17","PercentComplete":57.00,"TotalCount":0.0,"Children":2},
{"JobId":65,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL3_BOX","JobType":"box","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL_BOX","ParentJobId":1,"PredecessorJobName":null,"PredecessorJobId":null,"StartTime":"6/2/2014 5:00:06 PM","EndTime":"6/2/2014 5:07:42 PM","ElapsedTime":"00:07:36","JobStatusId":5,"JobStatus":"Success","MeanRunTime":"00:03:17","PercentComplete":100.0,"TotalCount":0.0,"Children":5}
]
Sample 3rd level data: 样本第3级数据:
[
{"JobId":265,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL2_S_CLEAN1","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL2_BOX","ParentJobId":63,"PredecessorJobName":"NRS_COL2_S_TOUCH1","PredecessorJobId":266,"StartTime":"","EndTime":"","ElapsedTime":"00:58:31","JobStatusId":7,"JobStatus":"On Ice","PercentComplete":null,"Children":0},
{"JobId":266,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL2_S_TOUCH1","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL2_BOX","ParentJobId":63,"PredecessorJobName":null,"PredecessorJobId":null,"StartTime":"","EndTime":"","ElapsedTime":"00:58:31","JobStatusId":7,"JobStatus":"On Ice","PercentComplete":null,"Children":0},
{"JobId":267,"AppId":1,"AppName":"APP1","LobId":2,"LobName":"LOB2","JobName":"NRS_COL2_A_ZFINSNAMA","TimeZone":"Chicago (Central Standard Time)","ParentJobName":"NRS_COL2_BOX","ParentJobId":63,"PredecessorJobName":"NRS_COL2_S_CLEAN1","PredecessorJobId":265,"StartTime":"6/2/2014 5:02:02 PM","EndTime":"","ElapsedTime":"00:58:31","JobStatusId":4,"JobStatus":"Running","PercentComplete":null,"Children":0}
]
I have no problem with the traditional parent-child hierarchy, but I'm struggling with even how I go about making the detail template behave for it's children. 我对传统的父子层次结构没有任何问题,但是我甚至在努力使细节模板为其子项运行。
I would like the template to be appropriate for child/grandchild display - no dropdown indicator if it does not have additional children. 我希望模板适合子/孙子显示 - 如果没有额外的孩子,则没有下拉指示器。 I assume that i can evaluate the data on databound, but I'm just not seeing how to do it.
我假设我可以评估数据绑定上的数据,但我只是没有看到如何做到这一点。
It took a while, but I finally worked out an answer with some guidance from the peeps at Telerik. 花了一段时间,但我终于在Telerik的窥视中得到了一些答案。 I was just having the hardest time getting my head around the solution.
我只是最难以解决问题。
Vladimir (at Telerik) suggested that I use a custom ajax call in the detailInit function using a function on success to determine if I had child data to consider. Vladimir(在Telerik)建议我使用成功函数在detailInit函数中使用自定义ajax调用来确定我是否要考虑子数据。 Since I needed the detail grid no matter what, I moved the child check into another function that creates the detail grid.
由于我无论如何都需要细节网格,因此我将子检查移动到另一个创建细节网格的函数中。 If I find child data, I add a detailInit parameter to the new grid.
如果我找到子数据,我会将detailInit参数添加到新网格中。 If not, I simply render the new detail grid.
如果没有,我只是渲染新的细节网格。
The ajax initDetail function: ajax initDetail函数:
function detailInit(e) {
var eventData = e;
$.ajax({
url: apiUrl + "ProcessJobs",
type: "POST",
data: {BoxId: e.data.JobId, AppId: e.data.AppId},
dataType: "json",
success: function(data, status, xhr) {
initializeDetailGrid(eventData, data);
}
}
The function to build the new detail grid with the check for children: 使用子项检查构建新详细信息网格的功能:
function initializeDetailGrid(e, result) {
var moreChildren = result[0].HasChildren;
var gridBaseOptions = {
dataSource: result,
scrollable: false,
sortable: true,
columns: [
{
field: "ParentJobId",
title: "Parent Job"
},
{
field: "JobId",
title: "Job Id"
},
{
field: "JobName",
title: "Job Name",
},
{
field: "JobStatus",
title: "Status"
},
{
field: "JobStatusId",
title: "Status Code"
},
{
field: "HasChildren",
title: "Has Children"
},
{
field: "ChildrenCount",
title: "Child Jobs"
}
]
};
var gridOptions = {};
if (moreChildren) {
gridOptions = $.extend({}, gridBaseOptions, { detailInit: detailInit });
} else {
gridOptions = gridBaseOptions;
};
$("<div/>").appendTo(e.detailCell).kendoGrid(gridOptions);
};
For completeness, here is the full page and example data from the sample project. 为了完整性,这里是示例项目的完整页面和示例数据。 It is a .Net MVC4 based website, using Web API services for data and Kendo UI for the client.
它是一个基于.Net MVC4的网站,使用Web API服务为客户端提供数据和Kendo UI。
Here is the page code: 这是页面代码:
@{
ViewBag.Title = "n-level Grid";
}
<script type="text/javascript">
$(document).ready(function () {
var isParent, appId, lobId, boxId;
var apiUrl = '@ViewBag.ApiUrl';
var lobDataSource = new kendo.data.DataSource({
transport: {
read: {
url: apiUrl + "Lob"
}
},
schema: {
model: {
id: "LobId",
hasChildren: "HasChildren"
}
}
});
var appsDataSource = new kendo.data.DataSource({
transport: {
read: {
url: apiUrl + "App"
},
parameterMap: function (data, action) {
if (action === "read") {
data.lobid = lobId;
data.parent = isParent;
return data;
} else {
return data;
}
}
}
});
var filterDataSource = new kendo.data.DataSource({
transport: {
read: {
url: apiUrl + "Theme"
}
},
schema: {
model: { id: "FilterId" }
}
});
var boxesDataSource = new kendo.data.DataSource({
transport: {
read: {
url: apiUrl + "Process"
},
parameterMap: function (data) {
data.appid = appId;
data.parent = isParent;
data.lobid = lobId;
return kendo.stringify(data);
}
},
schema: {
data: "Data",
total: "Total",
model: { id: "JobId" }
},
serverPaging: true,
serverFiltering: true,
serverSorting: true
});
var lobnav = $("#lobnav").kendoTreeView({
select: function (e) {
var tree = this;
var src = tree.dataItem(e.node);
lobId = src.LobId;
isParent = src.HasChildren;
},
change: function (e) {
appsDataSource.read();
},
dataSource: {
transport: {
read: {
url: apiUrl + "Lob"
}
},
schema: {
model: {
id: "LobId",
hasChildren: "HasChildren"
}
}
},
loadOnDemand: false,
dataTextField: "LobName"
});
var appnav = $("#lobapp").kendoListView({
selectable: "single",
autoBind: false,
change: function () {
var idx = this.select().index();
var itm = this.dataSource.view()[idx];
appId = itm.AppId;
boxesDataSource.query({
page: 1,
pageSize: 10
});
},
template: "<div class='pointercursor'>${AppName}</div>",
dataSource: appsDataSource
});
var jobsfilter = $("#jobfilter").kendoListView({
selectable: "single",
loadOnDemand: false,
template: "<div class='pointercursor' id=${FilterId}>${FilterName}</div>",
dataSource: filterDataSource,
dataBound: function () {
var dsource = $("#jobfilter").data("kendoListView").dataSource;
if (dsource.at(0).FilterName !== "All") {
dsource.insert(0, { FilterId: 0, FilterName: "All" });
}
},
change: function () {
var itm = this.select().index(), dataItem = this.dataSource.view()[itm];
var appDs = appsDataSource.view(), apps = $("#lobapp").data("kendoListView"),
selected = $.map(apps.select(), function (item) {
return appDs[$(item).index()].AppName;
});
if (selected.length > 0) {
if (dataItem.FilterId !== 0) {
var $filter = new Array();
$filter.push({ field: "JobStatusId", operator: "eq", value: dataItem.FilterId });
jgrid.dataSource.filter($filter);
} else {
jgrid.dataSource.filter({});
}
}
}
});
var jgrid = $("#boxesgrid").kendoGrid({
columns: [
{
field: "AppName",
title: "App"
},
{
field: "JobId",
title: "Job Id"
},
{
field: "JobName",
title: "Job Name",
},
{
field: "JobStatus",
title: "Status"
},
{
field: "JobStatusId",
title: "Status Code"
},
{
field: "HasChildren",
title: "Has Children"
},
{
field: "ChildrenCount",
title: "Child Jobs"
}
],
sortable: {
mode: "single",
allowUnsort: true
},
pageable: {
pageSizes: [10],
numeric: true,
refresh: true,
pageSize: 10
},
autoBind: false,
scrollable: false,
resizable: true,
detailInit: detailInit,
dataSource: boxesDataSource
}).data("kendoGrid");
function detailInit(e) {
var eventData = e;
$.ajax({
url: apiUrl + "ProcessJobs",
type: "POST",
data: {BoxId: e.data.JobId, AppId: e.data.AppId},
dataType: "json",
success: function(data, status, xhr) {
initializeDetailGrid(eventData, data);
}
});
};
function initializeDetailGrid(e, result) {
var moreChildren = result[0].HasChildren;
var gridBaseOptions = {
dataSource: result,
scrollable: false,
sortable: true,
columns: [
{
field: "ParentJobId",
title: "Parent Job"
},
{
field: "JobId",
title: "Job Id"
},
{
field: "JobName",
title: "Job Name",
},
{
field: "JobStatus",
title: "Status"
},
{
field: "JobStatusId",
title: "Status Code"
},
{
field: "HasChildren",
title: "Has Children"
},
{
field: "ChildrenCount",
title: "Child Jobs"
}
]
};
var gridOptions = {};
if (moreChildren) {
gridOptions = $.extend({}, gridBaseOptions, { detailInit: detailInit });
} else {
gridOptions = gridBaseOptions;
};
$("<div/>").appendTo(e.detailCell).kendoGrid(gridOptions);
};
});
</script>
<div class="col-md-2">
<div class="panel panel-default">
<div class="panel-heading">Line of Business</div>
<div class="panel-body" id="lobnav"></div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Application</div>
<div class="panel-body" id="lobapp"></div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Filter</div>
<div class="panel-body" id="jobfilter">
</div>
</div>
</div>
<div class="col-md-10">
<div id="boxesgrid"></div>
</div>
The data is actually hardcoded for this sample app, but I still return it via Web API. 这个示例应用程序的数据实际上是硬编码的,但我仍然通过Web API返回它。 Here is a sample of the highest level data:
以下是最高级别数据的示例:
new Process {JobId = 108, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_108", ParentJobName = null, ParentJobId = null, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 3, HasChildren = true},
new Process {JobId = 109, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_109", ParentJobName = null, ParentJobId = null, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 4, HasChildren = true},
new Process {JobId = 110, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_110", ParentJobName = null, ParentJobId = null, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 2, HasChildren = true},
new Process {JobId = 111, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_111", ParentJobName = null, ParentJobId = null, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 5, HasChildren = true},
Here is some second level data (child data): 这是一些二级数据(子数据):
new Process {JobId = 1037, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_1037", ParentJobName = "job_109", ParentJobId = 109, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 1038, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_1038", ParentJobName = "job_109", ParentJobId = 109, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 1039, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_1039", ParentJobName = "job_110", ParentJobId = 110, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 2, HasChildren = true},
new Process {JobId = 1040, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_1040", ParentJobName = "job_110", ParentJobId = 110, JobStatusId = 4, JobStatus = "Running", ChildrenCount = 2, HasChildren = true},
Some of the 3rd level data (grandchildren): 一些第三级数据(孙子):
new Process {JobId = 5000, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_5000", ParentJobName = "job_1039", ParentJobId = 1039, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 5001, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_5001", ParentJobName = "job_1039", ParentJobId = 1039, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 5002, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_5002", ParentJobName = "job_1040", ParentJobId = 1040, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 5003, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_5003", ParentJobName = "job_1040", ParentJobId = 1040, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 0, HasChildren = false},
new Process {JobId = 5004, AppId = 1, AppName = "App1", LobId = 2, LobName = "Lob2", JobName = "job_5004", ParentJobName = "job_1041", ParentJobId = 1041, JobStatusId = 5, JobStatus = "Success", ChildrenCount = 1, HasChildren = true},
And so on... 等等...
It is working correctly for 4 levels in my testing. 它在我的测试中正常工作4个级别。 There are formatting issues with the multiple nested grids that I will be addressing.
我将要解决的多个嵌套网格存在格式问题。
Not sure if this question is still open, but a simple solution is to use recursion in the "DetailInit" function, like below: 不确定这个问题是否仍然是开放的,但一个简单的解决方案是在“DetailInit”函数中使用递归,如下所示:
<!DOCTYPE html> <html> <head> <base href="https://demos.telerik.com/kendo-ui/grid/hierarchy"> <style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style> <title></title> <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.common-material.min.css" /> <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.material.min.css" /> <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2018.1.117/styles/kendo.material.mobile.min.css" /> <script src="https://kendo.cdn.telerik.com/2018.1.117/js/jquery.min.js"></script> <script src="https://kendo.cdn.telerik.com/2018.1.117/js/kendo.all.min.js"></script> </head> <body> <div id="example"> <div id="grid"></div> <script> var myData = [{ item: 0, title:"Meat" , items:[ { item: 0.1, title:"Beef"}, { item: 0.2, title:"Chicken"}, ]}, { item: 1, title:"Vegetables", items:[ { item: 1.0, title:"Carrot"}, { item: 1.1, title:"Pies", items:[ { item: 1.11, title:"Pie1"}, { item: 1.12, title:"Pie2"}, { item: 1.13, title:"Pie3"} ]}, ]} ]; $(document).ready(function() { var element = $("#grid").kendoGrid({ dataSource: { data: myData }, height: 600, sortable: true, pageable: true, detailInit: detailInit1, dataBound: function() { this.expandRow(this.tbody.find("tr.k-master-row").last()); }, columns: [ { field: "item", title: "ID", width: "110px" }, { field: "title", title: "Food", width: "110px" } ] }); }); function detailInit1(e) { $("<div/>").appendTo(e.detailCell).kendoGrid({ dataSource: { data: e.data.items //data is the current position item, items is its child items }, scrollable: false, sortable: true, pageable: true, detailInit: detailInit1, dataBound: function() { this.expandRow(this.tbody.find("tr.k-master-row").last()); }, columns: [ { field: "item", title: "ID", width: "110px" }, { field: "title", title: "Food", width: "110px" } ] }); } </script> </div> </body> </html>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.