jqGrid MVC HTML 助手






4.81/5 (24投票s)
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 的修复
- 修复了“datefmt”设置器逻辑中的一个错误(原始字符串值为“datafmt”)。
- 添加了用于“cellEdit”和“cellsubmit”的方法,用于选择性地编辑单元格数据。
- 在示例项目中添加了选择、文本框、日期时间复选框网格字段,用于在网格中执行数据更新。网格中现在有每种数据类型的字段,并且所有这些字段都可以编辑。
- 添加了提交/发布逻辑,通过添加到“_Grid”页面的按钮将编辑后的数据返回到服务器。
- 添加了交替行斑马条纹。
- 重新启用了 Site.css 文件,以获得更好的外观和感觉。
- 更改了模型,以更好地反映数据创建的方式(即,从后端控制器功能创建),同时在模型中添加了额外的隐藏字段,这些字段会在前后传递。
- 下载了最新版本的 jqGrid (4.4.1) 并将其集成到项目中,但未能成功解决下面讨论的问题。
一切运行正常,除了单元格编辑功能存在一个严重问题,我目前正在研究。具体细节如下:
如果您单击一个可编辑单元格,它将进入编辑模式,并且可以更改单元格内容。但是,如果您随后单击另一个单元格而不先按 Enter 键(无论您是否更改了单元格内容),单元格内容将被清除。原始值将无法恢复,也无法输入新值。对于复选框字段,情况更糟。如果您不在网格的第一行,无论您做什么,都会出现此问题。
此外,当通过单击按钮启动到服务器的提交过程时,用于保存 JSON 网格数据的隐藏字段的内容在由 JS 按钮逻辑设置后会损坏,并包含 HTML 标记数据(包括“<”字符),导致 ASP.Net 抛出“不安全的数据提交”错误页面。
在我看来,在更改后或甚至在离开可编辑字段之前需要按 Enter 键是荒谬的。这看起来像是一个 jqGrid 错误。请检查一下,并告知我是否有什么地方做得不对。