使用Jquery的Ajax dataGrid用户控件
使用Jquery的Ajax dataGrid用户控件。
引言
在第一部分,我描述了如何在 ASP.NET 页面中实现 Jquery Ajax 调用。 在本部分中,我将介绍使用 Jquery 和第一部分中的 Ajax 函数的 Ajax datagrid
用户控件。
背景
Ajax 网格控件实际上与 Microsoft 网格控件类似。 它有两个部分,分页索引和网格。 在网格中,有两个主要部分,标题和内容。 标题是静态的,并且在大多数时候在服务器中的资源文件中定义,内容是从数据库查询中动态获取的。 Microsoft datagrid
或 gridview
使用绑定方法绑定结果并在服务器端呈现。 我们的任务是绑定结果并在客户端呈现。
我没有使用 Microsoft Ajax 的原因是希望使 Ajax 更轻量、更灵活。 Microsoft Ajax 仍然会在服务器端绑定和渲染网格,这会消耗更多资源来构建和格式化网格,并且还会传输更多冗余数据,例如 HTML 标签、样式和静态命令名称或链接。 我的想法是,我们返回类似 JSON 格式结果的数据表给客户端,让客户端使用预定义的模式绑定结果,在客户端呈现表格,因此所有绑定、呈现、样式和自定义工作都将在客户端完成,从而使流量和服务器更轻量。
演示部分
为了使其工作,页面需要继承自 Jpage,我在第一部分中演示了它,因为它将触发 Ajax 调用,并且还使用一个 Ajax datagrid
,我将在下面演示。 网格看起来像这样
让我逐步描述 Ajax dataGrid
。
第 1 部分:在 ascx 文件中
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="JgridCtl.ascx.cs"
Inherits="YourNameSpace.UserControls.JgridCtl"
TargetSchema="<a href="http://schemas.microsoft.com/intellisense/ie5"%">
http://schemas.microsoft.com/intellisense/ie5"%</a>>
<div>
<span id="pageIdx"></span>
<span id="loading" class="hide">
<span class="ProgressText">loading......</span></span><br/>
<asp:PlaceHolder ID="JgridHolder" runat="server"></asp:PlaceHolder>
</div>
这非常简单,它包括一个 HTML span
,用于保存分页索引内容,一个 HTML span
,用于保存等待消息,以及一个 PlaceHolder
,用于保存 grid
。
第 2 部分:在代码后置,ascx.cs 文件中
C# 中有两个目标
- 将模板加载到
PlaceHolder
中,模板将保存基本的网格结构和标题; 这个想法就像<asp:TemplateColumn>
。 因为结构和标题取决于不同的页面。 - 为 JavaScript 初始化数据并加载 Ajax
datagrid
JS 文件。
代码如下所示(阅读注释)
namespace YourNameSpace.UserControls{
public class JgridCtl : System.Web.UI.UserControl
{
#region WebControls
protected PlaceHolder JgridHolder;
private ITemplate _template;
#endregion
#region Properties
//set a template to hold HTML table for header
[PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(TemplateControl))]
public ITemplate Template {
get { this.EnsureChildControls(); return _template; }
set { this.EnsureChildControls(); _template = value; }
}
//your JS file path
public string PathToJgridPageScript {
get { return Page.ResolveUrl("~/YourPath/JgridCtl.js") + "?version=" +
Config.Version; }
}
#endregion
#region Web Form Designer generated code
override protected void OnInit(EventArgs e) {
InitializeComponent();
//load the template into the placeHolder
if (_template != null) {
_template.InstantiateIn(JgridHolder);
}
base.OnInit(e);
}
...........
#endregion
#region Events
private void Page_Load(object sender, System.EventArgs e) {
//initialize javascript data
if (!Page.IsPostBack) {
StringBuilder sb = new StringBuilder();
sb.Append("var Jgrid={};");
sb.Append(string.Format("Jgrid.Pager='{0}';",
"Your resource string that construct the index"));
sb.Append(string.Format("Jgrid.NoRsult='{0}';",
"Your resource string that show when no query result"));
Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
"JgridCtl_Block", sb.ToString(), true);
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "JgridCtl",
PathToJgridPageScript);
}
}
#endregion
#region Helper Methods
//load column headers
internal void ColLocalize(string id, string resource) {
try {
Label tmpTxt;
tmpTxt = (Label)this.FindControl(id);
tmpTxt.Text = resource;
}catch(Exception){}
}
#endregion
}
}
第 3 部分:JS 文件
在阅读代码和注释时请耐心等待,尽管它不大(小于 4K)。
//TB and schema will be defined in page level, all these variables are
//essential and enforce to check in the code.
//TB is html table id, schema is the string array that must be matched with both
//table columns and JSON return headers (they are datatable headers or
//anonymous type properties in C# result)
var TB=schema=currentPg=currentPerPage=param=null;
//all these 3 functions must be defined in page level.
var pageFuns=['JgridReady','getParameter','customTable'];
//html methods, just a short cut to construct html.
function td(C){return '<td>'+C+'</td>';}
function pageLink(Idx,C){return(' '+link('javascript:goPage('+Idx+')',C,Idx));}
function link(L,C,N){
return('<a class="HyperLink" name="'+N+'" href="'+L+'">'+C+'</a>');}
//page methods
//remember the pageheader control in part one, when page is loaded,
//$(document).ready will forward the call to page level pageReady();
//we check these 3 essential functions whether are defined in page level
//here and fire another page level JgridReady().
function pageReady(){
var missing=[];
//check whether those 3 page functions above were defined in page level.
$.each(pageFuns,function(){if(!window[this]){missing.push(this);}})
if(missing.length>0){alert('missing function: '+missing.join());return;}
//call page level ready function.
JgridReady();
if(TB==null||schema==null){alert(
'Variable TB or schema is not initialized in JgridReady().');}
}
//a search click function must attach in page level searching button,
//call getParameter() to construct parameter object
function OnClickSearch(){
//call page level method to construct parameter.
getParameter();
if(param==null||currentPerPage==null){alert(
'Variable param or currentPerPage is not initialized in getParameter().');return;}
goPage(1);
}
//when page index click, fire Ajax call for query.
function goPage(pgNum){
//disable page index link and other controls to prevent user keep sending request.
enableCtls(false);
enablePgLinks(false);
currentPg=pgNum;
param.Page=pgNum;
//call server 'getList' method and return to callback method, so in the
//page level code behind, it must provide a 'protected getList()' method for ajax call,
//or you can improve it by setting the method name in variable to make it dynamic.
//callAjax() is define in Jpage in part one.
callAjax('getList',param,'getListCallBack');
}
function getListCallBack(result){
//in the result, it has 2 parts, the total amount of the result and the result list
//convert the JSON result list to an Object array match with its header.
var list=jstrToObjAry(result.list);
//construct the paging index link.
$('#pageIdx').html(rowCountHeader(result.total,currentPg,
list.length)+getPageLink(result.total));
//clear the old content before render
$('#'+TB).find('tr:gt(0)').remove().end().hide();
//build a plain table that match the result with the schema which
//defined in the page level
generateTable(TB,schema,list);
//formatting and styling the table
formatTable(TB);
//customize the table, do exact work that you want, like add command
//links in every row.
try{customTable(TB);}catch(e){}
enableCtls(true);
}
//enable or disable the control when Ajax call back or Ajax call processing
function enableCtls(F){
$('#loading').toggleClass('hide',F);
$('#btnSearch').attr('disabled',!F);
}
//enable or disable the paging index when Ajax call back or Ajax call processing
function enablePgLinks(F){$('#linkWrapper a').each(function(){(F)?$(this).attr(
'href','javascript:goPage('+$(this).attr('name')+')'):$(this).attr('href','#');});}
//format table, usually set some columns invisible(like id)
function formatTable(tbId){$('#'+tbId).find(' tr:first .hideCol').each(
function(){$('#'+tbId+' tbody td:nth-child('+($(
this).index()+1)+')').hide();}).end().show();}
//general methods
//this is optional, it gets some control value to construct parameter
function getSort(V){return (Val('select[id*=ddlSortExpr]')==V)?Val(
'select[id*=ddlSortOrder]'):'';}
function Val(selector){return $(selector).val();}
//get column index, the id is define in your page level, html table template.
function tdIdx(id){return $('#'+id).index();}
//build index header information
function rowCountHeader(total,pgNum,rowNum){
if(total<=0){return ("<span>"+Jgrid.NoRsult+"</span>");}
var startIndex=(pgNum-1)*currentPerPage+1;
return Jgrid.Pager.replace('{0}',startIndex).replace('{1}',
startIndex+rowNum-1).replace('{2}',total);
}
//build paging index
function getPageLink(total){
var idx1=(0!=currentPg%10)?currentPg-currentPg%10+1:(currentPg-10)+1;
var idxLast=Math.ceil(total/currentPerPage);
var A=[];
A.push('<span id="linkWrapper">');
if(1<idx1){A.push(pageLink((idx1-1),"..."));}
for(var i=idx1;i<idx1+10&&i<=idxLast;i++){if(i==currentPg){
A.push(' '+i);}else{A.push(pageLink(i,i));}}
if((idx1+10)<=idxLast){A.push(pageLink((idx1+10),'...'));}
A.push('</span>');
return A.join('');
}
//generic methods
//to create a plain table that map the result to the schema,
//if schema field is not found in the result, it will create
//a cell with undefine content, then you can edit it in customTable
//method in your page level after the grid is rendered.
function generateTable(tbId,colSchema,dataAry){
var A=[];
for(var i=0;i<dataAry.length;i++){
A.push('<tr '+((i&1)?'':'class="NormalGridItem"')+'
onmouseover="style.backgroundColor=\'#aaaaff\';"
onmouseout="style.backgroundColor=\'\';" >');
//insert cell by order that define in your schema and match with the result.
$.each(colSchema,function(n){A.push(td(dataAry[i][colSchema[n].toString()]));});
A.push('</tr>');
}
$(A.join('')).insertAfter('#'+tbId+' tr:first');
}
//construct an object array which maps your schema and return
//result header, if the schema field is not found in result, it will be nothing.
function jstrToObjAry(jsonStr){
var jAry=JSON.parse(jsonStr);
var oAry=[];
for(var i=1;i<jAry.length;i++){oAry.push(new objType(jAry,i));}
return oAry;
}
function objType(ary,n){for(var x=0;x<ary[0].length;x++){
this[ary[0][x].toString()]=ary[n][x];}}
它在页面中的工作方式
正如我在顶部提到的,页面必须是 Jpage
并使用此控件。
在 aspx 页面中,它通常应该像这样
<%@ Register TagPrefix="uc1" TagName="JgridCtl" Src="../UserControls/JgridCtl.ascx" %>
......
<script type="text/javascript" src="<%= PathToPageAjaxScript%>"></script>
<script type="text/javascript">
var pgData=<%=pageData%>;
var res=<%=resource%>;
</script>
......
<form id="Form1" method="post" defaultfocus="btnSearch" runat="server">
.......
<uc1:JgridCtl ID="JgridCtl1" runat="server">
<template>
<table id="tbLocation" class="simpleTable"
style="display:none;width:100%;margin-top:3px">
<tr class="NormalGridHeader">
<td id="idxCol"></td>
<td class="hideCol" id="IdCol">Id</td>
<td><asp:label id="ColCompany" runat="server"></asp:label></td>
<td><asp:label id="ColLocation" runat="server"></asp:label></td>
<td><asp:label id="ColType" runat="server"></asp:label></td>
<td><asp:label id="ColContact" runat="server"></asp:label></td>
<td><asp:label id="ColAddress" runat="server"></asp:label></td>
<td></td>
</tr>
</table>
</template>
</uc1:JgridCtl>
.......
</form>
如您所见,页面加载 Jgrid 控件并使用列标题定义您的 table
模板。
在 aspx.cs 中
//inherit from Jpage
public class Locations : Jpage
{
.....
//initial data for JavaScript
#region Properties
public string pageData { get; private set; }
public string resource { get; private set; }
public string PathToPageAjaxScript { get {
return Page.ResolveUrl("../YourPath/Locations.js") +
"?version=" + Config.Version; } }
#endregion
.....
private void Page_Load(object sender, System.EventArgs e)
{
//usually remove the viewstate to improve performance
Page.EnableViewState=false;
.....
if ( !Page.IsPostBack )
{
......
//initial resource for javascript
resource = serializer.Serialize(new{Edit = GetResource("[Edit]"),
Del = GetResource("[Delete]")});
//initial other variable for javascript
pageData = serializer.Serialize(new{
companyId=m_CompanyID,
editLink = this.Request.ApplicationPath +
"/YourPath/EditLocation.aspx?{0}&RetPath=" +
Global.GetEscapedUrl(this.Request.RawUrl),
});
}
....
}
#region Ajax methods
//the Ajax methods must be protected with return as part one demo
protected object getList() {
var result = YourLogic.Locations.Lookup(ParamId("Type"),
Param("Zip"), Param("Phone"), Param("Contact"), ParamId("PerPage"),
ParamId("Page"));
//construct the result set, filter result from the collection type by
//LINC and convert it to JSON format string.
//the anonymous type properties must map with the schema which defined
//in the JavaScript.
//after the serialize, the result looks like
//[["ID","Name","Company".....],["195","mylocation","my company"...],[....],.....]
var list = result.OfType<YourLogic.Location>().Select(i => new {
i.ID, i.Name, i.Company,i.TypeDesc,i.Contact,
Address=i.Address.FullAddressText }).ToList().ToDataTable().ToJsonString();
//return an anonymous type with result list and amount number
return new { total = result.VirtualRows, list };
}
}
ToDataTable()
和 ToJsonString()
是您需要的扩展方法
public static DataTable ToDataTable<T>(this IList<T> data) {
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name,
(prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() ==
typeof(Nullable<>)) ? Nullable.GetUnderlyingType(
prop.PropertyType) : prop.PropertyType);
foreach (T item in data) {
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
//we use Newtonsoft library here, you can write your own.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static string ToJsonString(this DataTable table)
{
JArray jsItems = new JArray();
// first row should be a Header containing field names
JArray jsItem = new JArray();
for (int i = 0; i < table.Columns.Count; i++)
jsItem.Add(table.Columns[i].ColumnName);
jsItems.Add(jsItem);
for (int r = 0; r < table.Rows.Count; r++) {
jsItem = new JArray();
for (int c = 0; c < table.Columns.Count; c++)
jsItem.Add(table.Rows[r][c].ToString());
jsItems.Add(jsItem);
}
return jsItems.ToString(Formatting.None);
}
在页面 JS 文件中
var cmdContent=null;
//page level ready function
function JgridReady(){
//html table id in the template
TB='tbLocation';
//the schema need match the order in the grid and the result header
schema=['Idx','ID','Company','Name','TypeDesc','Contact','Address'];
cmdContent=td(link('#',res.Edit,'Edit'));
}
//construct the parameter
function getParameter(){
currentPerPage=parseInt(Val('select[id*=ctlRows]'));
param={Type:Val('select[id^=ctlType]'),Status:Val('select[id^=ddlActive]'),
Resource:Val('#txtResource'),ComId:pgData.companyId,Company:Val(
'#txtCompany'),Phone:Val('#txtPhone'),Zip:Val('#txtZip'),Loc:Val(
'#txtLoc'),Contact:Val('#txtContact'),PerPage:currentPerPage};
}
//customize the table after it was created
function customTable(tbId){
var startRow=(currentPg-1)*currentPerPage+1;
var idxIdx=tdIdx('idxCol');
var idIdx=tdIdx('IdCol');
var rows=$('#'+TB+' tr:gt(0)');
var R=null;
for(var i=0;i<rows.length;i++){
R=rows[i];
//add the row index in every row, actually you can make it in the user control
R.cells[idxIdx].innerHTML=startRow+i;
//add a link command in every row
$(R).append(cmdContent.replace('#',pgData.editLink.replace('{0}',
'LocId='+R.cells[idIdx].innerHTML+'&CompanyId='+pgData.companyId)));
/*
//add a command dynamically in every row, if command link name 'Delete',
//it will create javascript call tbLocationDelete(row) dynamically.
$(R).append(cmdContent).find('a:last').click(function(e){e.preventDefault();
var funcName=TB+$(this).attr('name');window[funcName]($(this).closest('tr'))
;});
*/
}
}
现在,您可以看到实现此网格很容易,只需 3 个步骤
- 在 aspx 中导入它,并使用 HTML 表格为结构和标题定义一个模板。
- 在代码后置中创建带有返回值的受保护 Ajax 方法。
- 在 JS 文件中分配表 ID、模式,提供一个
getParameter()
来构造参数和customTable()
方法来定制表。
您可以根据需要改进此解决方案,它将更强大。 在下一部分,我将提供另一种解决方案,将您的 Ajax 网格数据导出到 Excel 文件。