创建可展开的主详细信息表 (jQuery DataTables 和 ASP.NET MVC 集成 - 第 IV 部分)






4.87/5 (54投票s)
使用 jQuery DataTables 插件在 ASP.NET MVC 中实现可展开的主详细信息表
引言
如果您使用 jQuery DataTables 插件,那么创建具有分页、排序和过滤功能的完整表格将是一项轻松的任务。该插件允许您仅用一行代码即可为普通的 HTML 表格添加所有这些功能,如下面的示例所示。
("#myTable").dataTables();
这一行代码会查找一个 ID 为“myTable
”的 HTML 表格,并添加分页、排序和关键字过滤功能。一个普通的 HTML 表格将被转换为一个功能齐全的表格,如下图所示。
在本文中,我们将了解如何将标准的 DataTable
扩展为可展开的主详细信息表。本文的目的是展示如何显示公司列表,其中包含一个展开按钮,该按钮将显示所选公司的员工列表,如下图所示。
在此示例中,表格中的每个记录(在此示例中为公司)都有一个展开按钮,点击此按钮时会打开详细信息。详细信息可以是子记录(例如,在此示例中为该公司工作的员工)、关于公司的其他详细信息(例如,标志、描述以及未在表格行中显示的其他信息),或两者的组合。
背景
当存在相关实体时,可展开表格通常是一个常见的需求。如果您有拥有员工的公司,有学生老师,有包含销售订单项的销售订单,那么您可能需要实现主记录列表(公司、老师、销售订单)的功能,并能够在展开主记录行时显示与之相关的记录。在其他情况下,您可能需要可展开的表格,即只在表格中显示最少数量的列,并且当用户点击一行时,会在展开的行中显示更多详细信息。这些需求可以使用 jQuery DataTables 插件轻松实现——本文将介绍如何配置它,添加哪些客户端逻辑,以及服务器端需要实现什么来支持客户端的可展开表格。
当实现可展开表格时,会使用以下场景:
- 在表格中添加一个额外的列,其中包含展开/折叠按钮,使用户能够展开选定的表格行。
- 当用户点击展开按钮时,会向服务器发送一个 AJAX 调用,并返回详细信息的 HTML。
- 返回的 HTML 使用
DataTables
的fnOpen
函数作为子行注入,并且展开按钮会转换为折叠按钮。 - 当用户点击折叠按钮时,会使用
DataTables
的fnClose
函数关闭子行,并且折叠按钮会转换回展开按钮。
本文将介绍如何使用 jQuery DataTables 插件实现此场景。
Using the Code
此示例展示了如何使用 jQuery DataTables
插件在 ASP.NET MVC 中实现可展开的主详细信息表。项目需要实现三个部分:
- 定义将要显示的数据结构的 Model。
Controller
类,用于响应DataTables
插件发送的请求。- View,它是渲染引擎,并且包含需要实现的客户端逻辑。
以下各节将对此进行描述。
模型
Model 表示为两个通过一对多关系关联的类。此示例使用公司作为主信息,员工作为详细信息。Model 类的源代码如下所示。
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public int CompanyID { get; set; }
}
员工与其公司之间的关系通过 Employee
类中的 CompanyID
属性建立。
控制器 (Controller)
Controller
类响应用户操作并返回响应。客户端会发送一个按公司 ID 获取 employees
列表的请求,当按下展开按钮时会发送此请求。此请求将包含显示在展开行中的 employees
列表的公司 ID。控制器中的 Action
方法返回按公司 ID 查找的员工,如下所示。
public class HomeController : Controller
{
public ActionResult CompanyEmployees(int? CompanyID)
{
var employees = DataRepository.GetEmployees();
var companyEmployees = (from e in employees
where (CompanyID == null || e.CompanyID == CompanyID)
select e).ToList();
return View(companyEmployees);
}
}
此 Action 方法获取公司的 ID,并查找所有具有该公司 ID 的 employees
。有一个用于格式化返回的公司列表的辅助 Partial View,如下所示。
@model IEnumerable<JQueryDataTables.Models.Employee>
<table cellpadding="4" cellspacing="0"
border="0" style="padding-left:50px;">
<tr>
<th>Employee</th>
<th>Position</th>
<th>Phone</th>
<th>Email</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@item.Name</td>
<td>@item.Position</td>
<td>@item.Phone</td>
<td>@item.Email</td>
</tr>
}
</table>
此 View 使用由控制器过滤的员工列表生成 HTML 表格。此 HTML 代码作为 AJAX 响应发送回客户端。下图显示了在 Firebug 中跟踪的 Home
/CompanyEmployees
调用的 AJAX 响应。
视图
View 页面用于显示表格并包含客户端 JavaScript。此示例使用了三个 View 页面:
- 静态 View 页面,其中公司表格是静态 HTML 表格。
- 服务器端 View,其中公司表格是在服务器端生成的。
- AJAX View 页面,其中公司表格是通过 AJAX 调用加载的。
然而,所有这些 View 都使用相同的客户端逻辑来显示详细信息(员工列表)——一个 AJAX 调用被发送到上面描述的控制器 Action /Home/CompanyEmployees,并且控制器返回一个 HTML 响应,该响应被注入到表格中。
所有三个 View 都使用相同的布局页面,其中包含它们所需的所有通用元素。该布局页面显示在以下列表中。
<html>
<head>
<title>Implementation of Master-Details tables using a JQuery DataTables plugin
</title>
<link href="@Url.Content("~/Content/dataTables/demo_page.css")"
rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/dataTables/demo_table.css")"
rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/dataTables/demo_table_jui.css")"
rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/themes/base/jquery-ui.css")"
rel="stylesheet" type="text/css" media="all" />
<link href="@Url.Content("~/Content/themes/smoothness/jquery-ui-1.7.2.custom.css")"
rel="stylesheet" type="text/css" media="all" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript">
</script>
<script src="@Url.Content("~/Scripts/jquery.dataTables.min.js")"
type="text/javascript"></script>
<script language="javascript" type="text/javascript">
$(document).ready(function () {
@RenderSection("onReady", required: false)
});
</script>
</head>
<body id="dt_example">
<div id="container">
<a href="/Home/Index">Static table</a> |
<a href="/Home/ServerSide">Server-side generated table</a> |
<a href="/Home/Ajax">Ajax-loaded table</a>
@RenderBody()
</div>
</body>
</html>
布局页面包括所有必要的 CSS/JavaScript 文件、定义的页面通用结构以及将在特定页面上定义的两个左侧部分。这些部分是:
onReady
部分,用于包含将在文档就绪事件上执行的自定义 JavaScript。Body
部分,用于放置每个页面的 HTML 代码。
在以下示例中,将描述三种不同的可展开表格使用场景。
静态 View
在静态 View 页面中,主公司表格被生成为一个普通的 HTML 表格。静态表格的 HTML 源代码显示在以下列表中。
<div id="demo">
<table id="companies" class="display">
<thead>
<tr>
<th></th>
<th>Company name</th>
<th>Address</th>
<th>Town</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="/Content/images/details_open.png"
rel="0" alt="expand/collapse"></td>
<td>Emkay Entertainments</td>
<td>Nobel House, Regent Centre</td>
<td>Lothian</td>
</tr>
<tr>
<td><img src="/Content/images/details_open.png"
rel="0" alt="expand/collapse"></td>
<td>The Empire</td>
<td>Milton Keynes Leisure Plaza</td>
<td>Buckinghamshire</td>
</tr>
...
</tbody>
</table>
</div>
此代码放置在 View 的 body
部分。在第一列中,放置了一个 rel
属性等于公司 ID 的图像。当用户点击此图像时,将向服务器端控制器发送一个 AJAX 调用,并以展开行的形式显示返回的 HTML。执行此操作的 JavaScript 代码位于 View 的 head 部分,如下所示。
var oTable;
$('#companies tbody td img').click(function () {
var nTr = this.parentNode.parentNode;
if (this.src.match('details_close')) {
/* This row is already open - close it */
this.src = "/Content/images/details_open.png";
oTable.fnClose(nTr);
}
else {
/* Open this row */
this.src = "/Content/images/details_close.png";
var companyid = $(this).attr("rel");
$.get("CompanyEmployees?CompanyID=" + companyid, function (employees) {
oTable.fnOpen(nTr, employees, 'details');
});
}
});
在上面的 JavaScript 中,为第一列中的每个图像添加了一个点击处理程序。当用户点击图像时,此代码会检查图像是否处于关闭状态。如果图像处于关闭状态,则会向服务器发送一个 AJAX 调用;返回的 HTML 使用 fnOpen
函数注入,并且图像被更改;否则,将使用 fnClose
函数关闭详细信息行。oTable
变量是对以下代码中初始化的 DataTable
对象的引用。
您需要添加 DataTables
初始化代码来初始化公司表格。这是使用 fnOpen
和 fnClose
函数所必需的。DataTable
初始化代码的示例显示如下。唯一的特定设置是使用 jQueryUI 进行样式设置,并使第一列(包含图像)不可排序且不可搜索。
/* Initialize table and make first column non-sortable*/
oTable = $('#companies').dataTable({ "bJQueryUI": true,
"aoColumns": [
{ "bSortable": false,
"bSearchable": false },
null,
null,
null
]
});
服务器端生成的 View
在本文的第二个示例中,company
表格是在服务器端动态生成的。View 页面的主体显示在以下列表中。
<div id="demo">
<table id="companies" class="display">
<thead>
<tr>
<th></th>
<th>Company Name</th>
<th>Address</th>
<th>Town</th>
</tr>
</thead>
@foreach (var item in Model) {
<tr>
<td><img src="/Content/images/details_open.png"
alt="expand/collapse" rel="@item.ID"/></td>
<td>@item.Name</td>
<td>@item.Address</td>
<td>@item.Town</td>
</tr>
}
</table>
</div>
如您所见,没有太大的区别——View 动态生成与上一个示例相同的结构。因此,onready
部分与上一个 View 相同。
请注意,此 View 使用公司列表来生成表格,因此 Action 方法应传递应用程序中所有公司的列表。用于此示例以将模型传递给 View 的 action
方法的示例显示在以下列表中。
public class HomeController : Controller
{
public ActionResult ServerSide()
{
return View(DataRepository.GetCompanies());
}
}
AJAX View 页面
本文的最后一个示例是一个通过 AJAX 调用动态加载公司的 View 页面。这是前一种情况的性能改进,如果您预计表格中的项目数量会很多,并且一次加载所有项目可能会导致性能问题,那么应该使用此方法。View 页面的 body
仅表示表格的结构,如下所示。
<div id="demo">
<table id="companies" class="display">
<thead>
<tr>
<th></th>
<th>Company name</th>
<th>Address</th>
<th>Town</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
包含初始化代码的 onready
部分稍作修改,如下所示。
var oTable;
$('#companies tbody td img').live('click', function () {
var nTr = this.parentNode.parentNode;
if (this.src.match('details_close')) {
/* This row is already open - close it */
this.src = "/Content/images/details_open.png";
oTable.fnClose(nTr);
}
else {
/* Open this row */
this.src = "/Content/images/details_close.png";
var companyid = $(this).attr("rel");
$.get("CompanyEmployees?CompanyID=" + companyid, function (employees) {
oTable.fnOpen(nTr, employees, 'details');
});
}
});
区别在于使用了 live
函数而不是直接的点击处理程序,因为事件应该添加到动态添加到每次重新加载的元素上。如果没有 live
函数,您将需要在每次从服务器端重新加载表格时应用点击处理程序。
DataTables
初始化调用也已更改——您需要将 bServerSide
参数设置为 true
,表示公司将从 AJAX 源加载,并且服务器端页面将提供表格数据。初始化调用显示在以下列表中。
/* Initialize table and make first column non-sortable*/
oTable = $('#companies').dataTable({
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": 'AjaxHandler',
"bJQueryUI": true,
"aoColumns": [
{ "bSortable": false,
"bSearchable": false,
"fnRender": function (oObj) {
return '/Content/images/details_open.png" ' +
'alt="expand/collapse" rel="' +
oObj.aData[0] + '"/>';
}
},
null,
null,
null
]
});
请注意,在第一列中,使用 fnRender
函数生成图像并将公司 ID 放在展开/折叠图像的 rel
属性中。在 ASP.NET MVC 中设置服务器端处理超出了本文的范围,因此我建议您查看 将 DataTables 集成到 ASP.NET MVC 应用程序中 这篇文章(这是该系列文章的第一篇)。
结论
本文介绍了如何使用 jQuery DataTables 插件和 ASP.NET MVC 创建可展开的主详细信息表。这是我撰写的关于将 jQuery DataTables 插件集成到 ASP.NET MVC 应用程序中的系列文章中的第四篇。其他可能让您感兴趣的文章有:
- 将 jQuery DataTables 插件集成到 ASP.NET MVC 应用程序中,描述了如何在 ASP.NET 中实现服务器端处理,以便使用 jQuery DataTables 插件实现高性能的服务器端表格。
- 在 ASP.NET MVC 中创建可编辑的 DataTables,描述了如何在数据表中实现添加、编辑和删除功能。
- 在 ASP.NET MVC 中创建表格之间的父子关系 - 一篇与本文类似的论文,解释了如何以父子关系连接两个表格。
该系列文章可能会让您能够使用 jQuery DataTables 插件在 ASP.NET MVC 中创建功能齐全且有效的表格。
历史
- 2011 年 5 月 1 日:初始版本