Introduction
In the first 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 to the Microsoft grid control. It has two parts, the paging index and the grid. In the grid, there is 2 main parts, header and content. Headers are static and defined in resource file in server in most of the time, contents are dynamic from database query. Microsoft datagrid
or gridview
uses binding method to bind the result and render in server side. Our task is to bind to result and render in client side.
The reason why I didn't use Microsoft Ajax is 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 predefined 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 needs to 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, it includes an HTML span
which holds paging index content, an HTML span
which holds waiting message and a PlaceHolder
which holds the grid
.
Part 2: In Code Behind, ascx.cs File
There are two goals in 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 depend on different pages. - Initialize data for JavaScript and load the Ajax
datagrid
JS file.
The code is shown below (read the comments):
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 while reading the code and comments, 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 mentioned on the top, the page must be Jpage
and use this control.
In aspx page, it should look 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 defines 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 that 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 the next part, I will provide another solution to export your Ajax grid data to Excel file.