65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (19投票s)

2015年11月3日

CPOL

4分钟阅读

viewsIcon

66865

downloadIcon

3881

一个完整的 MVC 示例,演示了如何在部分视图中使用模态对话框进行分页、过滤和排序

引言

最近,我不得不开发一个单页应用程序,其中菜单栏应该在页面的左侧,所有实体列表都应该作为部分视图加载在右侧的HTML DIV元素中。 此外,所有编辑、创建和详细视图都应该在模态对话框中加载。我决定将所有这些功能整合到一个完整的 MVC 示例中,并添加其他功能,如loadmask

 

使用代码

 

主页

Home Index 视图由三个 DIV 组成:logonavigationcontentFrame,如下所示:

<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。我们需要将ActionLinkclick事件绑定到调用CustomersControllerIndexaction,并将数据加载到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函数的末尾,我们应该设置pageSizepageNumber

int pageSize = 3;
int pageNumber = page ?? 1;

最后,将ToList更改为ToPagedList,并将两个参数pageNumberpageSize传递给它,并返回部分视图而不是视图。

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 ActionLinkbtnName属性设置为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});
    }
}

 

 

 

 

 

© . All rights reserved.