Introduction
In the 1st part, I describe how to implement Jquery Ajax call in the asp.net page;
In this part, I will introduce the Ajax datagrid user control by using Jquery and the Ajax function in part one.
Background
The Ajax grid control actually is similar with Microsoft grid control. It has 2 parts, the paging index and the grid. In the grid there is 2 main parts, header and content. Header are static and defined in resource file in server in most of time, content are dynamic from database query. Microsoft datagrid or gridview use binding method to bind the result and render in server side. Our task is bind to result and render in client side.
The reason why I didn't use Microsoft Ajax, because we want to make the Ajax lighter and more flexible. the Microsoft Ajax will still bind and render the grid in server side, that consumes more resource to build and format the grid and also transfer more redundant data like html tag, style and static command names or links. My idea is that we return datatable like JSON format result to client, let client bind the result with predefine schema, render table in client side, so all binding, rendering, styling and customization jobs will be done in client so that makes traffic and server lighter.
Demo Part by Part
To make it work, the page need inherit from the Jpage which I demo in the part one, because it will fire Ajax call, and also uses an Ajax datagrid which I will demo underneath. the grid looks like this:
Let me describe the Ajax dataGrid part by part:
Part 1: In ascx File
<%@ 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>
It is pretty simple, includes an HTML span which holds paging index content, a HTML span which holds waitting message and a PlaceHolder which holds the grid.
Part 2: In Code Beind, ascx.cs File
There is 2 goals in the C#:
- loading a template into the placeHolder, the template will hold the basic grid structure and header; the idea is like
<asp:TemplateColumn>
. Because the structure and the header are depend on different pages. - initialize data for JavaScript and load the Ajax datagrid JS file.
The code shown below (read the comment):
namespace YourNameSpace.UserControls{
public class JgridCtl : System.Web.UI.UserControl
{
#region WebControls
protected PlaceHolder JgridHolder;
private ITemplate _template;
#endregion
#region Properties
[PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(TemplateControl))]
public ITemplate Template {
get { this.EnsureChildControls(); return _template; }
set { this.EnsureChildControls(); _template = value; }
}
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();
if (_template != null) {
_template.InstantiateIn(JgridHolder);
}
base.OnInit(e);
}
...........
#endregion
#region Events
private void Page_Load(object sender, System.EventArgs e) {
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
internal void ColLocalize(string id, string resource) {
try {
Label tmpTxt;
tmpTxt = (Label)this.FindControl(id);
tmpTxt.Text = resource;
}catch(Exception){}
}
#endregion
}
}
Part 3: JS File
Be patient to read the code and comment, it is not big though(less than 4K).
var TB=schema=currentPg=currentPerPage=param=null;
var pageFuns=['JgridReady','getParameter','customTable'];
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>');}
function pageReady(){
var missing=[];
$.each(pageFuns,function(){if(!window[this]){missing.push(this);}})
if(missing.length>0){alert('missing function: '+missing.join());return;}
JgridReady();
if(TB==null||schema==null){alert(
'Variable TB or schema is not initialized in JgridReady().');}
}
function OnClickSearch(){
getParameter();
if(param==null||currentPerPage==null){alert(
'Variable param or currentPerPage is not initialized in getParameter().');return;}
goPage(1);
}
function goPage(pgNum){
enableCtls(false);
enablePgLinks(false);
currentPg=pgNum;
param.Page=pgNum;
callAjax('getList',param,'getListCallBack');
}
function getListCallBack(result){
var list=jstrToObjAry(result.list);
$('#pageIdx').html(rowCountHeader(result.total,currentPg,
list.length)+getPageLink(result.total));
$('#'+TB).find('tr:gt(0)').remove().end().hide();
generateTable(TB,schema,list);
formatTable(TB);
try{customTable(TB);}catch(e){}
enableCtls(true);
}
function enableCtls(F){
$('#loading').toggleClass('hide',F);
$('#btnSearch').attr('disabled',!F);
}
function enablePgLinks(F){$('#linkWrapper a').each(function(){(F)?$(this).attr(
'href','javascript:goPage('+$(this).attr('name')+')'):$(this).attr('href','#');});}
function formatTable(tbId){$('#'+tbId).find(' tr:first .hideCol').each(
function(){$('#'+tbId+' tbody td:nth-child('+($(
this).index()+1)+')').hide();}).end().show();}
function getSort(V){return (Val('select[id*=ddlSortExpr]')==V)?Val(
'select[id*=ddlSortOrder]'):'';}
function Val(selector){return $(selector).val();}
function tdIdx(id){return $('#'+id).index();}
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);
}
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('');
}
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=\'\';" >');
$.each(colSchema,function(n){A.push(td(dataAry[i][colSchema[n].toString()]));});
A.push('</tr>');
}
$(A.join('')).insertAfter('#'+tbId+' tr:first');
}
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];}}
How it Works in the Page
As I metioned on the top, the page must be Jpage and use this control.
In aspx page it should looks like this normally:
<%@ 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>
As you see, page loads the Jgrid control and define your table template with column headers.
In aspx.cs
public class Locations : Jpage
{
.....
#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)
{
Page.EnableViewState=false;
.....
if ( !Page.IsPostBack )
{
......
resource = serializer.Serialize(new{Edit = GetResource("[Edit]"),
Del = GetResource("[Delete]")});
pageData = serializer.Serialize(new{
companyId=m_CompanyID,
editLink = this.Request.ApplicationPath +
"/YourPath/EditLocation.aspx?{0}&RetPath=" +
Global.GetEscapedUrl(this.Request.RawUrl),
});
}
....
}
#region Ajax methods
protected object getList() {
var result = YourLogic.Locations.Lookup(ParamId("Type"),
Param("Zip"), Param("Phone"), Param("Contact"), ParamId("PerPage"),
ParamId("Page"));
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 new { total = result.VirtualRows, list };
}
}
ToDataTable()
and ToJsonString()
are extension methods you need:
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;
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static string ToJsonString(this DataTable table)
{
JArray jsItems = new JArray();
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);
}
In page JS File
var cmdContent=null;
function JgridReady(){
TB='tbLocation';
schema=['Idx','ID','Company','Name','TypeDesc','Contact','Address'];
cmdContent=td(link('#',res.Edit,'Edit'));
}
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};
}
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];
R.cells[idxIdx].innerHTML=startRow+i;
$(R).append(cmdContent.replace('#',pgData.editLink.replace('{0}',
'LocId='+R.cells[idIdx].innerHTML+'&CompanyId='+pgData.companyId)));
}
}
Now, you can see it is easy to implement this grid, 3 steps only:
- import it in aspx and define a template with html table for structure and header.
- create protected Ajax methods with return in code behind.
- assign table id, schema in JS file, provide a
getParameter()
to construct parameters and customTable()
method to customize table.
You can improve this solution as you like, it will be more powerful. In next part I will provide another solution to export your ajax grid data to Excel file.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.