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

jqGrid MVC HTML 助手

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (24投票s)

2012年8月21日

CPOL

5分钟阅读

viewsIcon

104002

downloadIcon

5842

MVC 3 jqGrid HTML 辅助函数。

引言

JQGRID HTML 辅助函数是一个服务器端代码包装器,用于为 MVC 3 应用程序使用的 jqGrid 生成 JavaScript。该 HTML 辅助函数提供了 jqGrid 的所有功能,如 AJAX JSON 数据绑定、JSONP、编辑、过滤和自定义。

JQGRID 插件是一个功能齐全的开源 HTML 网格控件。在 MVC 应用程序中使用 jqGrid 需要一些繁琐的手动编码。HTML 辅助函数简化了这个过程,并提供了一个一致的接口。

背景

JQGRID HTML 辅助函数提供了一种简单一致的方式来渲染 JQGRID JAVASCRIPT,从而在网页中构建网格。该辅助函数专为 MVC 3 网页设计。数据绑定是 JQGRID 文档定义的任何数据类型。优选使用 JSON 数据类型并通过 AJAX 回到控制器检索数据。在文档就绪时,HTML 辅助函数创建的 JavaScript 函数将检索 JSON 数据并渲染网格内容。HTML 辅助函数会自动在页面上创建 2 个 <DIV> 元素,一个用于网格,另一个用于分页器。JSON 数据应包含元数据信息,以便能够正确地渲染具有分页功能的网格。

jQGRID HTML 支持子网格、自定义子网格和自定义树状网格。子网格是一种递归方法,可以在父网格内渲染一个网格。当展开自定义子网格时,会从控制器检索一个部分视图。自定义树状网格也会检索部分视图,包括最后一级。

使用代码

在视图中,通过添加 HTML 辅助函数命名空间来创建基本网格,然后添加网格控件,添加列,设置 AJAX URL 调用并创建控制器方法。

@using Mvc.HtmlHelpers
@(
    Html.jqGrid("AccountList")
 
    // columns
    .addColumn(new Column("AccountNumber").setLabel("AccountNumber").setWidth(100).setSortable(true))
    .addColumn(new Column("AccountName").setLabel("AccountName").setWidth(250).setSortable(true).setEditable(true))
    .addColumn(new Column("AccountDate").setLabel("Date").setWidth(70).setSortable(true))
    .addColumn(new Column("AccountType").setLabel("Type").setWidth(80).setSortable(true))
    .addColumn(new Column("AccountBalance").setLabel("Balance").setWidth(80).setSortable(true))
 
    // settings
    .setCaption("Account")
    .setRequestType(RequestType.get)
    .setUrl("~/Home/GetAccountList/")
    .setAutoWidth(true)
    .setHeight(400)
    .setRowNum(10)
    .setRowList(new int[]{10,15,20,50})
    .setViewRecords(true)
    .setSortName("AccountNumber")        
    .setSortOrder(SortOrder.asc)
    .setPager("pagerAccountList")
    .setPgButtons(true)
       
    // render the html
    .Render()
)

创建带有 AJAX 回调 URL 到控制器的视图。JSON 结果包含元数据,如总页数 (total)、当前页 (page)、总记录数 (records) 和行 (data content)。

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetAccountList(GridSettings gridSettings)
{
    // retrieve the sorted data
    List<Models.Account> accounts = Models.Account.GetAccounts();

    // create json data
    int pageIndex = gridSettings.pageIndex;
    int pageSize = gridSettings.pageSize;
    int totalRecords = accounts.Count;
    int totalPages = (int)Math.Ceiling((float)totalRecords / (float)pageSize);

    int startRow = (pageIndex - 1) * pageSize;
    int endRow = startRow + pageSize;

    var jsonData = new
    {
        total = totalPages,
        page = pageIndex,
        records = totalRecords,
        rows =
        (
            from Models.Account a in accounts
            where a.Row >= startRow && a.Row <= endRow
            select new
            {
                id = a.AccountNumber,
                cell = new string[] 
                {                            
                    a.AccountNumber, 
                    a.AccountName, 
                    a.AccountDate, 
                    a.AccountType, 
                    a.AccountBalance
                }
            }
        ).ToArray()
    };

    return Json(jsonData, JsonRequestBehavior.AllowGet);
}

自定义子网格是一个具有子网格的网格,该子网格会回调到控制器以检索部分视图。

@using Mvc.HtmlHelpers
@(
    Html.jqGrid("CustomGrid")
                        
    // columns
        .addColumn(new Column("AccountNumber").setLabel("AccountNumber").setWidth(100).setSortable(true))
        .addColumn(new Column("AccountName").setLabel("AccountName").setWidth(250).setSortable(true).setEditable(true))
        .addColumn(new Column("AccountDate").setLabel("Date").setWidth(70).setSortable(true))
        .addColumn(new Column("AccountType").setLabel("Type").setWidth(80).setSortable(true))
        .addColumn(new Column("AccountBalance").setLabel("Balance").setWidth(80).setSortable(true).setAlign(Align.right))
 
    // settings
    .setUrl("~/Home/GetAccountList/")
    .setHeight(600)
    .setRowNum(20)
    .setRowList(new int[] { 20, 50, 100, 200, 300 })
    .setPager("CustomGridAccountPager")
    .setPrint(true)
    .setLoadText("")
 
    // error handler
    .onLoadError("Error loading Account List")
    
    // events
    .onBeforeRequest(@"
        var grid = $('#CustomGridAccount');
        grid.jqGrid('setLabel', 'AccountBalance', '', {'text-align':'right'});
    ")
 
     // subgrid
    .setCustomGrid(
        new jqGrid("CustomGridDetails")
        .setUrl("~/Home/CustomGridDetails?rowId='+row_id+'")
        .onLoadError("CustomGrid Account Details")
        .setSubGridOptions("{plusicon : 'ui-icon-circle-plus', minusicon : 'ui-icon-circle-minus'}")
    )
                        
    // render the html
    .Render()
)
自定义子网格的详细信息只是一个包含 <tr> 和 <td> 的部分视图,从控制器返回的 HTML 被附加到父 <tr>。
@using jqGrid.Sample.Models
@model List<AccountDetail>
@{
    Layout = null;  
}
@foreach (AccountDetail detail in Model)
{
    <tr class="subGridRow">
        <td>
        </td>
        <td colspan="7">
            <a href="#" style="color: #0000FF">@detail.CLIENT_NAME</a>
        </td>
    </tr>
    <tr>
        <td>
        </td>
        <td colspan="7">
            <input type="checkbox" />
             <b>Portfolio: @detail.PORTFOLIO_NAME</b>
        </td>
    </tr>
    foreach (PortfolioDetail pd in detail.PortfolioDetails)
    {
        <tr>
            <td>
            </td>
            <td colspan="3">
                <table width="100%">
                    <tr>
                        <td>
                            <input type="checkbox" />
                        </td>
                        <td style="vertical-align: top; white-space: nowrap;">@pd.ACCOUNT_NAME</td>
                        <td style="vertical-align: top; width: 50">@pd.ACCOUNT_TYPE
                        </td>
                        <td style="vertical-align: top; width: 50">@pd.PROGRAM
                        </td>
                        <td>
                            <div style="white-space: nowrap; text-overflow: ellipsis; width: 15em; overflow: hidden;">@pd.INVESTMENT</div>
                        </td>
                    </tr>
                </table>
            </td>
            <td valign="top" align="right">
                <a href="#" style="vertical-align: bottom; padding-right: 7px">@pd.TOTAL_ASSETS.ToString("c0")</a>
            </td>
            <td valign="top" align="right">
                <a href="#" style="vertical-align: bottom; padding-right: 7px">@pd.CASH.ToString("c0")</a>
            </td>
            <td valign="top" align="right">
                <a href="#" style="vertical-align: bottom; padding-right: 7px">@pd.MSG_FEES.ToString("c0")</a>
            </td>
            <td valign="top" align="right">
                <a href="#" style="vertical-align: bottom; padding-right: 7px">@string.Format("{0:0.00%}", @pd.MSG_FEE_PCT / 100)</a>
            </td>
        </tr>
    }
    <tr class="subGridRow@(ViewData["RowId"])">
        <td>
        </td>
        <td colspan="7">
            <div style="padding-left: 32px">
                <div style="float: left; padding-bottom: 3px; padding-top: 3px">@Html.DropDownList(
                      "ddlListItems", new SelectList(new[] { new AccountDetail() { CLIENT_NAME = "Test" } }))</div>
                <div style="float:left">
                    <span class="btnStyleLeft"></span>
                    <div class="btnStyleCenter">
                        <div style="margin-top: 5px! important">
                            GO
                        </div>
                    </div>
                    <span class="btnStyleRight"></span>
                </div>
            </div>
        </td>
    </tr>    
}
<tr class="subGridRow@(ViewData["RowId"])">
    <td>
    </td>
    <td colspan="7">
        <img width="12" height="12" src="@(Url.Content("~/Images/yield.png"))" /> Ineligible
        security purchase
    </td>
</tr>

自定义树状有三个部分:主网格、级别子网格和详细级别网格。这些都是部分视图。

@using Mvc.HtmlHelpers
@(
    Html.jqGrid("CustomTree")
    // columns
    .addColumn(new Column("Name").setLabel("Organization").setWidth(350))
    .addColumn(new Column("AssetAllocation").setLabel("Asset Allocation"))
    .addColumn(new Column("ClientReview").setLabel("Client Review"))
    .addColumn(new Column("IneligibleActivity").setLabel("Ineligible Activity"))
    .addColumn(new Column("InvestmentGuideline").setLabel("Investment Guideline"))
    .addColumn(new Column("BillableAssets").setLabel("Billable Assets"))
    .addColumn(new Column("NonEarnedPcs").setLabel("Non-Earned PCs"))
 
    // settings
    .setRequestType(RequestType.get)
    .setUrl("~/Home/CustomTree")
    .setHeight(600)
 
    .setPager("CustomTreePager")
    .setLoadText("")
 
    .setCustomGrid(
        new jqGrid("CustomTreeLevel")
        .setUrl("~/Home/CustomTreeLevel?rowId='+row_id+'")
        .setSubGridOptions("{plusicon : 'ui-icon-plus', minusicon : 'ui-icon-minus', openicon:'ui-icon-carat-1-sw'}")
    )
 
    
    // render grid
    .Render()
)
<script type="text/javascript">
    function toggleExpCol(elementId, row_id) {
        var iconElement = $('#' + elementId);
 
        if (iconElement.hasClass('ui-icon-plus')) {
            iconElement.removeClass('ui-icon-plus');
            iconElement.addClass('ui-icon-minus');
 
            $.ajax(
            {
                type: 'get',
                contentType: 'application/json; charset=utf-8',
                url: "@Url.Content("~/Home/CustomTreeLevel?rowId=")" + row_id + '',
                success: function (data, textStatus) {
                    var newTr = $(data);
                    $(newTr).each(function (i) {
                        $(this).attr('isExpanded', false);
                        $(this).attr('parent', row_id);
                    });
                    $($('#CustomTree tr#' + row_id)).attr('isExpanded', true);
                    $($('#CustomTree tr#' + row_id)).after(newTr);
                }
            });
        }
        else {
            iconElement.removeClass('ui-icon-minus');
            iconElement.addClass('ui-icon-plus');
            var grid = $("#CustomTree").jqGrid();
 
            var getChildrenNode = function (row_id) {
                var result = [];
                var children = $(grid).find('tr[parent=' + row_id + ']');
                $(children).each(function (i) {
                    if ($(this).attr("isExpanded") == "true") {
                        var chl = getChildrenNode(this.id);
                        $(chl).each(function (i) {
                            result.push(this);
                        });
                    }
                    result.push(this);
                });
                return result;
            };
 
            var childern = getChildrenNode(row_id);
            $.each(childern, function (index, value) { $(value).remove(); });
        }
    }
</script>

自定义树状级别是一个由父 ID 属性控制的部分视图。点击函数在主视图中,用于切换展开和折叠。

@using Mvc.HtmlHelpers
@using jqGrid.Sample.Models
@model List<Organization>
@{
    Layout = null;
    string rowId = (string)ViewData["RowId"];
    int level = Convert.ToInt32(rowId.Split('_')[0]) + 1;
    int width = (int)(12 * level * 1.5);
    int left = width - 18;
}
@foreach (Organization org in Model)
{
    <tr id="@(org.Id)" class="ui-widget-content" role="row">
        <td></td>
        <td title="@(org.Name)" style="width: @(width)px;" role="gridcell">
            <table>
                <tr>
                    <td class="treeclick" align="right">
                        <div style="width: @(width)px;">
                            <div id="div_@(org.Id)" class="ui-icon ui-icon-plus" style="left: @(left)px;"/>
                            <script type="text/javascript">
                                $("#div_@(org.Id)").click(function () {
                                    toggleExpCol("div_@(org.Id)", "@(org.Id)");
                                });
                            </script>
                        </div>
                    </td>
                    <td title="@(org.Name)" style="white-space: nowrap" role="gridcell">@org.Name
                    </td>
                </tr>
            </table>
        </td>
        <td role="gridcell">@org.AssetAllocation</td>
        <td role="gridcell">@org.ClientReview</td>
        <td role="gridcell">@org.IneligibleActivity</td>
        <td role="gridcell">@org.InvestmentGuideline</td>
        <td role="gridcell">@org.BillableAssets</td>
        <td role="gridcell">@org.NonEarnedPcs</td>
    </tr>
}

 更新:GridSettings.cs 

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;


namespace Mvc.HtmlHelpers
{
    public enum OPERATION
    {
        none,
        add,
        del,
        edit,
        excel
    }

    [ModelBinder(typeof(GridModelBinder))]
    public class GridSettings
    {
        public int pageIndex { get; set; }
        public int pageSize { get; set; }
        public string sortColumn { get; set; }
        public string sortOrder { get; set; }
        public bool isSearch { get; set; }
        public string id { get; set; }
        public string param { get; set; }
        public string editOper { get; set; }
        public string addOper { get; set; }
        public string delOper { get; set; }
        public Filter where { get; set; }
        public OPERATION operation { get; set; }
    }

    public class GridModelBinder : IModelBinder
    {

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;
            return new GridSettings()
            {
                isSearch = bool.Parse(request["_search"] ?? "false"),
                pageIndex = int.Parse(request["page"] ?? "1"),
                pageSize = int.Parse(request["rows"] ?? "10"),
                sortColumn = request["sidx"] ?? "",
                sortOrder = request["sord"] ?? "asc",
                id = request["id"] ?? "",
                param = request["oper"] ?? "",
                editOper = request["edit"] ?? "",
                addOper = request["add"] ?? "",
                delOper = request["del"] ?? "",
                where = Filter.Create(request["filters"] ?? ""),
                operation = (OPERATION)System.Enum.Parse(typeof(OPERATION), request["oper"] ?? "none")
            };
        }

    }

    [DataContract]
    public class Filter
    {
        [DataMember]
        public string groupOp { get; set; }
        [DataMember]
        public Rule[] rules { get; set; }

        public static Filter Create(string jsonData)
        {
            try
            {
                var serializer = new DataContractJsonSerializer(typeof(Filter));
                System.IO.StringReader reader = new System.IO.StringReader(jsonData);
                System.IO.MemoryStream ms = new System.IO.MemoryStream(Encoding.Default.GetBytes(jsonData));
                return serializer.ReadObject(ms) as Filter;
            }
            catch
            {
                return null;
            }
        }
    }

    [DataContract]
    public class Rule
    {
        [DataMember]
        public string field { get; set; }
        [DataMember]
        public string op { get; set; }
        [DataMember]
        public string data { get; set; }
    }
}

为了支持 (CUD) 添加、编辑和删除,Grid 设置已更新,以处理添加、编辑、删除和其他操作。在视图中,有诸如

    .setEditUrl("~/Home/SaveGridUpdate/")
    .setNavAdd(true)
    .setNavDel(true)
    .setNavEdit(true) 

设置导航方法 (.setNav) 是在分页行上启用添加、编辑和删除按钮。控制器会在行被编辑时获得保存 CUD 的回调。使用网格设置操作来检查是执行添加、编辑还是删除操作。

public void SaveGridUpdate(GridSettings gridSettings)
{
    string id = gridSettings.id;
    switch(gridSettings.operation)
    {
        case OPERATION.add:
        {
            // do create
            break;
        }
        case OPERATION.edit:
        {
            // do update
            break;
        }
        case OPERATION.del:
        {
            // do delete
            break;
        }
    }
}

GridSettings.id 是被修改的行 ID,还可以通过 Request 对象检索所有已更新的字段。请在保存回调方法上设置一个调试断点,以确定如何检索这些字段。

值得关注的点  

http://www.trirand.com/jqgridwiki/doku.php
jqgrid wiki 提供了所有可用选项的丰富文档。

HTML 辅助函数包含了最常用的功能,但并非所有功能都已在此实现。欢迎您扩展此辅助函数。

Richard 的修复

  1. 修复了“datefmt”设置器逻辑中的一个错误(原始字符串值为“datafmt”)。
  2. 添加了用于“cellEdit”和“cellsubmit”的方法,用于选择性地编辑单元格数据。
  3. 在示例项目中添加了选择、文本框、日期时间复选框网格字段,用于在网格中执行数据更新。网格中现在有每种数据类型的字段,并且所有这些字段都可以编辑。
  4. 添加了提交/发布逻辑,通过添加到“_Grid”页面的按钮将编辑后的数据返回到服务器。
  5. 添加了交替行斑马条纹。
  6. 重新启用了 Site.css 文件,以获得更好的外观和感觉。
  7. 更改了模型,以更好地反映数据创建的方式(即,从后端控制器功能创建),同时在模型中添加了额外的隐藏字段,这些字段会在前后传递。
  8. 下载了最新版本的 jqGrid (4.4.1) 并将其集成到项目中,但未能成功解决下面讨论的问题。
  9. 一切运行正常,除了单元格编辑功能存在一个严重问题,我目前正在研究。具体细节如下:

    如果您单击一个可编辑单元格,它将进入编辑模式,并且可以更改单元格内容。但是,如果您随后单击另一个单元格而不先按 Enter 键(无论您是否更改了单元格内容),单元格内容将被清除。原始值将无法恢复,也无法输入新值。对于复选框字段,情况更糟。如果您不在网格的第一行,无论您做什么,都会出现此问题。

    此外,当通过单击按钮启动到服务器的提交过程时,用于保存 JSON 网格数据的隐藏字段的内容在由 JS 按钮逻辑设置后会损坏,并包含 HTML 标记数据(包括“<”字符),导致 ASP.Net 抛出“不安全的数据提交”错误页面。

    在我看来,在更改后或甚至在离开可编辑字段之前需要按 Enter 键是荒谬的。这看起来像是一个 jqGrid 错误。请检查一下,并告知我是否有什么地方做得不对。

© . All rights reserved.