使用模态对话框在部分视图中进行 MVC 分页、过滤和排序






4.91/5 (19投票s)
一个完整的 MVC 示例,演示了如何在部分视图中使用模态对话框进行分页、过滤和排序
引言
最近,我不得不开发一个单页应用程序,其中菜单栏应该在页面的左侧,所有实体列表都应该作为部分视图加载在右侧的HTML DIV元素中。 此外,所有编辑、创建和详细视图都应该在模态对话框中加载。我决定将所有这些功能整合到一个完整的 MVC 示例中,并添加其他功能,如loadmask。
使用代码
主页
Home Index 视图由三个 DIV 组成:logo、navigation 和contentFrame,如下所示:
<table>
<tr>
<td colspan="2">
<div id="logo" </div>
</td>
</tr>
<tr style="height:600px">
<td id="navigation" style="width:200px; ">
<div>
</div>
</td>
<td>
<div id="contentFrame" />
</td>
</tr>
</table>
navigation DIV 包含菜单栏,所有部分视图都应加载在contentFrame DIV 中。因此,我们在navigation DIV 中放置了一个ActionLink。
@Html.ActionLink("Customers", "Index", "Customers", new { },
new { id = "btnCustomers", @class = "btn btn-default btn-xs" })
由于我们需要通过 JavaScript 查找此元素,因此我们可以将其id属性设置为btnCustomers。我们需要将ActionLink的click事件绑定到调用CustomersController的Indexaction,并将数据加载到contentFrame中。
$(function () {
//find ActionLink throuth its id
$('#btnCustomers').click(function () {
//show loadmask when it is loading data
$('#contentFrame').mask("waiting ...");
//load data in to contentFrame DIV
$('#contentFrame').load(this.href, function (response, status, xhr) {
//to disappear loadmask after loading finished
$('#contentFrame').unmask("waiting ...");
});
return false;
});
});
由于加载部分视图可能需要一些时间,因此在加载数据时在contentFrame内部显示加载遮罩(loadmask)是更好的选择。
$('#contentFrame').mask("waiting ...");
加载完部分视图后,应该使用此函数调用来隐藏loadmask。
$('#contentFrame').unmask("waiting ...");
我们需要在 Web 应用程序中包含这些 JS 和 CSS 文件。
~/Scripts/jquery.mask.js
~/Content/jquery.loadmask.css
您可以像我一样将这两个文件放在BundleConfig.cs中。我们还应该将此图像文件放在应用程序中。
~/images/loading.gif
有关 JQuery loadmask 的更多信息,请参阅此网址:
https://code.google.com/p/jquery-loadmask/
分页
对于分页,您可以通过在应用程序中安装NuGet包管理器来使用pagedlist.mvc包。我们需要在 customer Index 视图的顶部放置这些代码行。
@using PagedList.Mvc
@model PagedList.IPagedList<CompleteMVCExample.Models.Customer>
在视图的末尾,我们需要放置分页控件。
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
<div id="myPager">
@Html.PagedListPager(Model, page => Url.Action("Index",
new { page, sort = ViewBag.CurrentSort, search = ViewBag.CurrentSearch }))
</div>
由于分页控件位于部分视图内,因此我们需要将分页控件的click事件绑定到加载数据到contentFrame。
$('#myPager').on('click', 'a', function (e) {
//prevent action link normal functionality
e.preventDefault();
$('#contentFrame').mask("waiting ...");
//ajax call index controller action
$.ajax({
url: this.href,
type: 'GET',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
//load returned data inside contentFrame DIV
$('#contentFrame').html(result);
}
});
});
再次,您可以使用 mask 和 unmask 函数来使您的应用程序更美观。在控制器端,我们需要向index控制器操作添加一个名为 page 的可空整数参数。
public ActionResult Index(int? page)
在index函数的末尾,我们应该设置pageSize和pageNumber。
int pageSize = 3;
int pageNumber = page ?? 1;
最后,将ToList更改为ToPagedList,并将两个参数pageNumber和pageSize传递给它,并返回部分视图而不是视图。
return PartialView(customers.ToPagedList(pageNumber, pageSize));
排序
当第一次单击每个列的标题时,列表将按该列的升序排序;如果再次单击,列表将按降序排序。因此,我们首先需要将列标题更改为ActionLink。
@Html.ActionLink("Customer Name", "Index", new { search = ViewBag.CurrentSearch,
sort = ViewBag.CurrentSort == "CustomerName_ASC" ? "CustomerName_DESC" : "CustomerName_ASC" },
new { @class = "SortButton" })
然后处理 click 事件,通过 ajax 调用 index 操作,并将数据加载到 contentFrame 中。
//wire up all sort ActionLinks throuth their class name
$(".SortButton").click(function (e) {
e.preventDefault();
$('#contentFrame').mask("waiting ...");
$.ajax({
url: this.href,
type: 'POST',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
//load returned data inside contentFrame DIV
$('#contentFrame').html(result);
}
})
});
在控制器端,您可以使用任何方法来排序您的列表。这里我使用了这个简单的方法。
if(!String.IsNullOrWhiteSpace(sort))
{
if(sort == "CustomerName_ASC")
customers = customers.OrderBy(c => c.CustomerName);
else if (sort == "CustomerName_DESC")
customers = customers.OrderByDescending(c => c.CustomerName);
else if (sort == "Address_ASC")
customers = customers.OrderBy(c => c.Address);
else if (sort == "Address_DESC")
customers = customers.OrderByDescending(c => c.Address);
else if (sort == "Phone_ASC")
customers = customers.OrderBy(c => c.Phone);
else if (sort == "Phone_DESC")
customers = customers.OrderByDescending(c => c.Phone);
}
else
{
customers = customers.OrderBy(c => c.CustomerName);
}
过滤
我只选择了 Customer Name 来过滤我的列表,所以我们需要在视图中添加一个textbox和一个button。
<td>@Html.TextBox("search", ViewBag.CurrentSearch as string, new { @class = "form-control" }) </td>
<td>@Html.ActionLink("Filter", "Index", new { sort = ViewBag.CurrentSort, search = "xyz" },
new { @class = "btn btn-default", btnName = "FilterCustomer" })</td>
我们需要将 click 事件绑定到调用index操作,如果成功,则将返回的数据加载到contentFrame中。
//find filter ActionLink throuth HTML5 attribute named btnName
$("a[btnName=FilterCustomer]").click(function (e) {
e.preventDefault();
var search = $('input[name=search]').val();
this.href = this.href.replace('xyz', search);
$('#contentFrame').mask("waiting ...");
$.ajax({
url: this.href,
type: 'POST',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
$('#contentFrame').html(result);
}
});
});
创建操作
Create, Edit 和 Details控制器操作彼此相似,因此我将只描述 create 操作。在视图的开头,我们为表单定义了控制器名称、操作和方法。
@using (Html.BeginForm("Create", "Customers", FormMethod.Post, new { id = "myForm" }))
在 Customer Index 视图内部,为 CreateActionLink添加 btnName = "btnCreate" ,以便通过 JQuery 找到此元素。
@Html.ActionLink("Create New", "Create", new { id = -1 },
new { btnName = "btnCreate", @class = "btn btn-default" })
我们需要将它的click事件绑定到调用稍后描述的setDialogLink JavaScript 函数。
$(function () {
//……
$.ajaxSetup({ cache: false });
setDialogLink($('a[btnName=btnCreate]'),'Create Customer', 500, 600, "contentFrame",
//…..
函数setDialogLink接收以下六个参数:
- element:action link 的 javascript 对象
- dialogTitle:模态对话框的标题
- dialogHeight:模态对话框的高度
- dialogWidth:模态对话框的宽度
- updateTargetId:内容 DIV 的 ID
- updateUrl:保存数据后应重新加载的 index 控制器操作的 URL
并绑定click事件以创建模态对话框。
function setDialogLink(element, dialogTitle, dialogHeight, dialogWidth, updateTargetId, updateUrl) {
// Wire up the click event of any dialog links
element.on('click', function () {
// Generate a unique id for the dialog div
var dialogId = 'uniqueName-' + Math.floor(Math.random() * 1000)
var dialogDiv = "<div id='" + dialogId + "'></div>";
// Load the form into the dialog div
$(dialogDiv).load(this.href, function () {
$(this).dialog({
modal: true,
resizable: false,
title: dialogTitle,
height: dialogHeight,
width: dialogWidth,
buttons: {
"Save": function () {
// Manually submit the form
var form = $('form', this);
$(form).submit();
},
"Cancel": function () {
$(this).dialog('close');
}
},
close: function () {
// It turns out that closing a jQuery UI dialog
// does not actually remove the element from the
// page but just hides it. For the server side
// validation tooltips to show up you need to
// remove the original form the page
$('#' + dialogId).remove();
}
});
// Enable client side validation
$.validator.unobtrusive.parse(this);
// Setup the ajax submit logic
wireUpForm(this, updateTargetId, updateUrl);
});
return false;
});
}
wireUpForm函数在setDialogLink内部被调用,以绑定模态对话框内表单的提交函数。
function wireUpForm(dialog, updateTargetId, updateUrl) {
$('form', dialog).submit(function () {
// Do not submit if the form
// does not pass client side validation
if (!$(this).valid())
return false;
// Client side validation passed, submit the form
// using the jQuery.ajax form
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
// Check whether the post was successful
if (result.success) {
// Close the dialog
$(dialog).dialog('close');
// Reload the updated data in the target div
$("#" + updateTargetId).load(updateUrl);
//$(this).dialog('destroy').remove()
} else {
// Reload the dialog to show model errors
$(dialog).html(result);
// Enable client side validation
$.validator.unobtrusive.parse(dialog);
// Setup the ajax submit logic
wireUpForm(dialog, updateTargetId, updateUrl);
}
}
});
return false;
});
}
我们不需要更多地更改HttpPost Create操作。在这里,我们需要在保存数据成功时返回json对象。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CustomerID,CustomerName,Phone,Address")] Customer customer)
{
if (ModelState.IsValid)
{
db.Customers.Add(customer);
db.SaveChanges();
return Json(new {success = true});
}
return PartialView(customer);
}
删除
我们将Delete ActionLink的btnName属性设置为btnDelete,以便通过 JavaScript 找到它。
@Html.ActionLink("Delete", "Delete", new { id = item.CustomerID },
new { @class = "btn btn-default btn-xs" , btnName="btnDelete"})
然后,将其click事件绑定到调用Delete控制器操作。在这里,我们不需要打开模态对话框,但我们需要获得用户关于删除数据的确认。为此,我们使用标准的 JavaScriptconfirm函数。
$('a[btnName=btnDelete]').click(function (e) {
e.preventDefault();
var confirmResult = confirm("Are you sure?");
if (confirmResult) {
$('#contentFrame').mask("waiting ...");
$.ajax(
{
url: this.href,
type: 'POST',
data: JSON.stringify({}),
dataType: 'json',
traditional: true,
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data.success) {
//reload data inside contentFrame
$('#contentFrame').load("/Customers/Index");
}
else {
//show error message
alert(data.errormessage);
}
$('#contentFrame').unmask("waiting ...");
},
error: function (data) {
alert("An error has occured!!!");
$('#contentFrame').unmask("waiting ...");
}
});
}
})
在HttpPost Delete操作中,即使删除记录失败,我们也需要返回 json 对象。
[HttpPost]
public ActionResult Delete(int id)
{
try
{
var customer = db.Customers.Find(id);
db.Customers.Remove(customer);
db.SaveChanges();
return Json(new {success = true, Message =""});
}
catch (Exception exp)
{
return Json(new { success=false, ErrorMessage=exp.Message});
}
}