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

使用Jquery的Ajax dataGrid用户控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (3投票s)

2011年10月4日

CPOL

3分钟阅读

viewsIcon

32796

使用Jquery的Ajax dataGrid用户控件。

引言

第一部分,我描述了如何在 ASP.NET 页面中实现 Jquery Ajax 调用。 在本部分中,我将介绍使用 Jquery 和第一部分中的 Ajax 函数的 Ajax datagrid 用户控件。

背景

Ajax 网格控件实际上与 Microsoft 网格控件类似。 它有两个部分,分页索引和网格。 在网格中,有两个主要部分,标题和内容。 标题是静态的,并且在大多数时候在服务器中的资源文件中定义,内容是从数据库查询中动态获取的。 Microsoft datagridgridview 使用绑定方法绑定结果并在服务器端呈现。 我们的任务是绑定结果并在客户端呈现。

我没有使用 Microsoft Ajax 的原因是希望使 Ajax 更轻量、更灵活。 Microsoft Ajax 仍然会在服务器端绑定和渲染网格,这会消耗更多资源来构建和格式化网格,并且还会传输更多冗余数据,例如 HTML 标签、样式和静态命令名称或链接。 我的想法是,我们返回类似 JSON 格式结果的数据表给客户端,让客户端使用预定义的模式绑定结果,在客户端呈现表格,因此所有绑定、呈现、样式和自定义工作都将在客户端完成,从而使流量和服务器更轻量。

演示部分

为了使其工作,页面需要继承自 Jpage,我在第一部分中演示了它,因为它将触发 Ajax 调用,并且还使用一个 Ajax datagrid,我将在下面演示。 网格看起来像这样

demo_size1600x1200-small.jpg

让我逐步描述 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# 中有两个目标

  1. 将模板加载到 PlaceHolder 中,模板将保存基本的网格结构和标题; 这个想法就像 <asp:TemplateColumn>。 因为结构和标题取决于不同的页面。
  2. 为 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 个步骤

  1. 在 aspx 中导入它,并使用 HTML 表格为结构和标题定义一个模板。
  2. 在代码后置中创建带有返回值的受保护 Ajax 方法。
  3. 在 JS 文件中分配表 ID、模式,提供一个 getParameter() 来构造参数和 customTable() 方法来定制表。

您可以根据需要改进此解决方案,它将更强大。 在下一部分,我将提供另一种解决方案,将您的 Ajax 网格数据导出到 Excel 文件。

© . All rights reserved.