Published:
30 Jan 2009
|
Abstract
In this article Mudassar examines the creation of a custom AJAX enabled Grid using JavaScript. After providing a brief introduction, he provides detailed coverage of both the client and server side techniques with the help of complete source code. He makes use of XML HTTP for making AJAX calls to the ASP.NET Page and then parses the received XML and retrieves the HTML from the tags and binds it to a DIV using the INNERHTML property. He also adds Paging and Sorting functionality to the control with AJAX effects and wraps the HTML in XML to enable sending separate HTML content in separate tags. |
|
by Mudassar Ahmed Khan
Feedback
|
Average Rating:
Views (Total / Last 10 Days):
43608/
54
|
|
|
Introduction |
In this article I have described how to build your own Ajax
Enabled Grid using JavaScript. The concept is that using simple HTML table
designs a Grid like control with Ajax Paging and Sorting functionality without
using any ASP.NET Controls.
Getting Started
We will start our Website by creating a New Website in
Visual Studio 2005 and Name it as AJAXGrid.
Then in the App_Data Folder keep the NorthWind.MDF which
will act as a Database for the project.
Now in the Web.Config add the following key.
Listing 1
<connectionStrings>
<add name ="conString"
connectionString ="Data Source=.\SQLEXPRESS;database=Northwind;
AttachDbFileName=|DataDirectory|\NORTHWND.MDF;
Integrated Security=true"/>
</connectionStrings>
|
Creating the HTML Table |
Now we will create the structure of the Grid. We will start
with the Header of the Grid.
Note that I have already created a CSS File which you will
find the same in the sample code.
Listing 2
<div id = "dvHeader">
<table id = 'tblHeader' cellpadding = "0" cellspacing = "0" width = '800px'>
<tr class = 'header'>
<td class = "headercell">Customer ID</td>
<td class = "headercell">Company Name</td>
<td class = "headercell">Contact Name</td>
<td class = "headercell">Contact Title</td>
<td class = "headercell">City</td>
</tr>
</table>
</div>
Then we will create the div with runat = "server"
tag which will act as content. The runat = "server" tag will allow
the div to be accessed server side. Since first time when the page loads the
HTML markup will be binded using the innerHTML property of DIV.
Listing 3
<div id = "dvContent" runat ="server" >
</div>
Finally, the third and the last part will act as a Pager for
the Grid.
Listing 4
<div id = "dvPager" runat = "server" style =" text-align:right; width:800px" >
</div>
|
Populating and Binding Data |
Next, we will create a function to populate the data from
the database into the DataTable.
The function accepts the query and returns a DataTable.
Listing 5
Private Function GetData(ByVal strQuery As String) As DataTable
Dim dt As New DataTable
Dim strConnString As String = System.Configuration.ConfigurationManager. _
ConnectionStrings("conString").ConnectionString
Dim con As New SqlConnection(strConnString)
Dim cmd As New SqlCommand(strQuery, con)
Dim sda As New SqlDataAdapter
cmd.CommandType = CommandType.Text
Try
con.Open()
sda.SelectCommand = cmd
sda.Fill(dt)
Catch ex As Exception
Return dt
Finally
con.Close()
cmd.Dispose()
sda.Dispose()
End Try
Return dt
End Function
Now we will populate the html table and bind it to the Div
dvContainer using the function.
It accepts the following Parameters:
dv – DataView of the Populated Datatable
intPageNo – Current Page Index
intRowsPerPage – Number of Records Per Page
It returns the Populated HTML Table as a String.
In the function I calculate the Start Index and the End
Index depending on Page No and the Number of Records per Page. The Start Index
and End index helps to loop through the DataView Records and select the
appropriate Record Set for the Corresponding Page.
Listing 6
intRowCount = dv.Count
intStartIndex = ((intPageNo - 1) * intRowsPerPage)
intEndIndex = (intPageNo * intRowsPerPage) - 1
When the Start Index and End Index have been calculated, a
loop is run on the DataView in order to build a HTML Table using String
Builder, thus binding the HTML Table with the data.
Listing 7
strResults.Append( _
" <table id = 'tblContent' cellpadding = '0' cellspacing = '0' width = '800px'>")
The complete function is give below.
Listing 8
Private Function PopulateGrid(ByVal dv As DataView, ByVal intPageNo As Integer, _
ByVal intRowsPerPage As Integer) As String
Dim strResults As New StringBuilder
Dim intStartIndex, intEndIndex, intRowCount As Integer
'Do the Paging Calculation
intRowCount = dv.Count
intStartIndex = ((intPageNo - 1) * intRowsPerPage)
intEndIndex = (intPageNo * intRowsPerPage) - 1
If intRowCount <= intEndIndex Then
intEndIndex = intRowCount - 1
End If
strResults.Append( _
" <table id = 'tblContent' cellpadding = '0' cellspacing = '0' width = '800px'>")
For i As Integer = intStartIndex To intEndIndex
If i Mod 2 = 0 Then
'Edit Row
strResults.Append("<tr class = 'rowstyle'>")
Else
'Alternating Rpw
strResults.Append("<tr class = 'alternatingrowstyle'>")
End If
strResults.Append("<td class = 'rowcell'>")
strResults.Append(dv.item(i)("CustomerID").ToString())
strResults.Append("</td>")
strResults.Append("<td class = 'rowcell'>")
strResults.Append(dv.item(i)("CompanyName").ToString())
strResults.Append("</td>")
strResults.Append("<td class = 'rowcell'>")
strResults.Append(dv.item(i)("ContactName").ToString())
strResults.Append("</td>")
strResults.Append("<td class = 'rowcell'>")
strResults.Append(dv.Item(i)("ContactTitle").ToString())
strResults.Append("</td>")
strResults.Append("<td class = 'rowcell'>")
strResults.Append(dv.Item(i)("City").ToString())
strResults.Append("</td>")
strResults.Append("</tr>")
Next
strResults.Append("</table>")
Return strResults.ToString()
End Function
|
Creating the Pager for the Grid |
This function, as the name suggests, creates the pager for
our HTML Grid. It accepts the following Parameters:
intRowCount – Total No of Records in the DataView
intPageNo – Current Page Index
intRowsPerPage – Number of Records Per Page
intPageSet – Index of the Current Page
Here you will notice that I have used a word called PageSet.
It is a set of Pages in the pager .
Figure 1
The above figure refers to a PageSet with index 1. It is
showing 10 Pages since I have set the intPagesPerSet to 10.
First, I am calculating the total number for pages for the
current set of data. This is done by dividing the Total Number of Records with
the Number of records per Page
Listing 9
dblPageCount = Convert.ToDecimal(intRowCount) / Convert.ToDecimal(intRowsPerPage)
intPageCount = Convert.ToInt32(Math.Ceiling(dblPageCount))
Then I am calculating the start page index and the end page
index of the PageSet based on the intPageSet and the intPagesPerSet. Note that
I have set the Pages per Set to 10. This means that on the Grid I will see Page
Numbers 1 to 10, in the next set 11 to 20 and so on.
Listing 10
intPagesPerSet = 10
intStartPage = ((intPageSet - 1) * intPagesPerSet) + 1
intEndPage = intPageSet * intPagesPerSet
As you can see, I am creating an HTML markup here too, using
a String builder which we will be binding to the dvPager. See the complete
function below.
Listing 11
Private Function Pager(ByVal intRowCount As Integer, ByVal intPageNo As Integer, _
ByVal intRowsPerPage As Integer, ByVal intPageSet As Integer) As String
Dim strResults As New StringBuilder
Dim intPageCount As Integer
Dim dblPageCount As Double
dblPageCount=Convert.ToDecimal(intRowCount)/Convert.ToDecimal(intRowsPerPage)
intPageCount = Convert.ToInt32(Math.Ceiling(dblPageCount))
If intPageNo > intPageCount Or intPageNo < 1 Then
intPageNo = 1
End If
Dim intStartPage, intEndPage, intPagesPerSet As Integer
intPagesPerSet = 10
intStartPage = ((intPageSet - 1) * intPagesPerSet) + 1
intEndPage = intPageSet * intPagesPerSet
If intEndPage > intPageCount Then
intEndPage = intPageCount
End If
If intPageSet > 1 Then
strResults.Append( _
"<a href = 'javascript:;' onclick = 'PrepareRequest(")
strResults.Append(intStartPage - intPagesPerSet)
strResults.Append(", ")
strResults.Append(intPageSet - 1)
strResults.Append(")' class='pager'><<<</a> ")
End If
For k As Integer = intStartPage To intEndPage
If k = intPageNo Then
strResults.Append("<span class='pager'>")
strResults.Append(k)
strResults.Append("</span>")
Else
strResults.Append( _
"<a href = 'javascript:;' onclick = 'PrepareRequest(")
strResults.Append(k)
strResults.Append(", ")
strResults.Append(intPageSet)
strResults.Append(")' class='pager'>")
strResults.Append(k)
strResults.Append("</a>")
Dim str As String = strResults.ToString
End If
strResults.Append(" ")
Next
If intPageCount > intEndPage Then
strResults.Append( _
"<a href = 'javascript:;' onclick = 'PrepareRequest(")
strResults.Append(intEndPage + 1)
strResults.Append(", ")
strResults.Append(intPageSet + 1)
strResults.Append(")' class='pager'>>>></a>")
End If
Return strResults.ToString()
End Function
|
Structure of the XML Response |
The structure of the XML Response is given below. The
following are the details of the tags:
ERROR – default Value False. If Error occurs then the value
is True.
PAGER – contains the HTML for the Pager DIV
GRID – contains the HTML for the Content DIV
Listing 12
<RESPONSE>
<VALUES>
<ERROR></ERROR>
<PAGER></PAGER>
<GRID></GRID>
</VALUES>
</RESPONSE>
|
Sending the Response |
In the page load, the response is sent to the page. When the
page is loaded the generated HTML is directly binded to the DIV using the
innerHTML property (see the code below).
Listing 13
dvContent.InnerHtml = PopulateGrid(dt.DefaultView, intPage, intRowsPerPage)
dvPager.InnerHtml = Pager(dt.Rows.Count, intPage, intRowsPerPage, intPageSet)
If the Parameters are received through the querystring, that
is it is an AJAX call, the Response XML is generated and sent back to the
client. Note I have used <![CDATA[ ]]>. Since if you directly embed html
within XML tags, the XML structure will be broken meaning if you place it
within these tags, the Parser ignores it.
Listing 14
Dim strResults As String = PopulateGrid(dt.DefaultView, intPage, intRowsPerPage)
Dim strPager As String = Pager(dt.Rows.Count, intPage, intRowsPerPage, intPageSet)
Dim strResponse As StringBuilder = New StringBuilder
strResponse.Append("<RESPONSE><VALUES><ERROR>False</ERROR><PAGER><![CDATA[")
strResponse.Append(strPager)
strResponse.Append("]]></PAGER><GRID><![CDATA[")
strResponse.Append(strResults)
strResponse.Append("]]></GRID></VALUES></RESPONSE>")
Response.Clear()
Response.ContentType = "text/xml"
Response.Write(strResponse.ToString())
Response.End()
|
Client Side Scripting for the Grid |
Now I will discuss the Client Side functionality on the Grid
which allows it to make AJAX Calls to the server.
The function below is used to send an Asynchronous Request
to the page using XMLHttpRequest Object. The function state_Change is called as
soon as the onreadystatechange of the XMLHttpRequest object is fired meaning
the object is ready to receive data. The complete function is given below.
Listing 12
function ReloadData(url)
{
xmlhttp=null;
if (window.XMLHttpRequest)
{// code for all new browsers
xmlhttp=new XMLHttpRequest();
}
else if (window.ActiveXObject)
{// code for IE5 and IE6
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
if (xmlhttp!=null)
{
xmlhttp.onreadystatechange=state_Change;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
else
{
alert("Your browser does not support XMLHTTP.");
}
}
The state_Change function verifies that the request is
loaded without any errors. If everything is fine, it calls the ParseXML
function which parses the XML Reponse received.
Listing 13
function state_Change()
{
if (xmlhttp.readyState==4)
{// 4 = "loaded"
if (xmlhttp.status==200)
{// 200 = OK
ParseXML(xmlhttp.responseXML.documentElement);
}
else
{
alert("Problem retrieving XML data");
}
}
}
The ParseXML function, as the name suggests, parses the XML
received from the server.
As you can see, it checks the tags and then extracts the
data using the GetNodeData function.
The ERROR Tag has value when some exceptions occurs on
server side. If an error occurs, the Error Message is displayed in the Error
Label lblError.
Listing 14
function ParseXML(xml)
{
if (xml!=null)
{
var Error = xml.getElementsByTagName('ERROR')[0];
if (Error == null || GetNodeData(Error) == "False")
{
//alert(Grid);
var Pager = xml.getElementsByTagName('PAGER')[0];
var Grid = xml.getElementsByTagName('GRID')[0];
if (Grid != null && Pager!= null)
{
//alert(Grid);
document.getElementById ("dvContent").innerHTML=GetNodeData(Grid);
document.getElementById ("dvPager").innerHTML=GetNodeData(Pager);
}
}
else
{
document.getElementById ("lblError").innerText = GetNodeData(Error);
}
}
}
function GetNodeData(node)
{
return (node.textContent || node.innerText || node.text) ;
}
Now I will explain how the paging and sorting requests are
prepared. When you click on the pager hyperlink the PrepareRequest function
prepares the URL by attaching the following query string parameters.
1) SortBy
2) SortOrder
3) Page
4) PageSet
And finally it calls the ReloadData function and passes the
prepared URL to it.
Listing 15
function PrepareRequest(Page, PageSet)
{
var ddlSort = document.getElementById("ddlSort");
var ddlSortOrder = document.getElementById("ddlSortOrder");
if (ddlSort.options[ddlSort.selectedIndex].value == "")
{
ddlSortOrder.options[0].selected = true;
ddlSortOrder.disabled = true;
}
else
{
ddlSortOrder.disabled = false;
}
var url = "Default.aspx?SortBy=" + ddlSort.options[ddlSort.selectedIndex].value + _
"&SortOrder=" + ddlSortOrder.options[ddlSortOrder.selectedIndex].value + "&Page=" _
+ Page + "&PageSet=" + PageSet;
ReloadData(url);
|
Sorting Functionality |
For Sorting I have used two dropdowns:
For displaying the Fields on which sorting is to be done
For displaying the sort order - Ascending or Descending
The dropdown, which shows the Field, picks up the values
from the Header Table tblHeader.
The Header Text could be anything, but make sure you place
it in correct order since it uses the index of the Column to identify the Column
Name in the DataTable. For example, if the CustomerID is the first Column, that
is index value 0 in the DataTable. So it should be first here so that the index
value is picked up correctly.
Listing 16
function PopulateDropDown()
{
var tblHeader = document.getElementById("tblHeader");
var ddlSort = document.getElementById("ddlSort");
var tblCells = tblHeader.getElementsByTagName("TD");
for (var i=0; i<tblCells.length;i++)
{
var opt = document.createElement("option");
ddlSort.options.add(opt);
opt.text = tblCells[i].innerHTML;
opt.value = i;
}
}
|
Downloads |
[Download Source]
You can download the code sample attached. It is tested in
the following browsers:
Internet Explorer 7
FireFox 3
Google Chrome
|
|
|
|
User Comments
Title:
wow
Name:
Sue
Date:
2012-10-11 3:05:48 PM
Comment:
what a great code! Thanks
|
Title:
m,n
Name:
mn,mn
Date:
2012-08-31 8:55:59 AM
Comment:
mn,mn
|
Title:
Excellent
Name:
Soons
Date:
2010-02-13 12:45:27 AM
Comment:
The example was very simple and informative.. Thanks for the good work
|
Title:
Small Problem
Name:
IndSoft
Date:
2009-08-30 10:12:21 AM
Comment:
Good work, im also tried this it was work for me, but my problem is i need to edit the record using ajax (Not using UpdatePanel in VS)what is best way to do that.
note-when i click edit link, it should take particular record to separate web control (textboxes).
|
Title:
RE i did upgrade it to 3.5
Name:
Mudassar Khan
Date:
2009-03-15 1:00:13 AM
Comment:
Hi, You ahould review the code after the upgrade. Since cannot say what has changed in that. You can checkout whether the data is assigned to div or not.
|
Title:
???
Name:
ericnickus@roadrunner.com
Date:
2009-03-14 7:34:38 PM
Comment:
i did upgrade it to 3.5
|
Title:
???
Name:
ericnickus@roadrunner.com
Date:
2009-03-14 7:34:04 PM
Comment:
I tried it in Visual Studio 2008 and it did compile, but didn't show any data in the latest version of firefox
??
|
Title:
good work
Name:
ajax helper
Date:
2009-02-06 5:16:16 PM
Comment:
http:// good work ajaxnetforum.blogspot.com/
ajax forum
|
Title:
Awesome!
Name:
ajax learner
Date:
2009-02-06 5:15:12 PM
Comment:
super work!
|
Title:
Good work
Name:
Muhsin
Date:
2009-02-04 5:09:07 AM
Comment:
Excellent tutorial for people who wants to understand how things work behind the scenes
|
Title:
Excellent Effort
Name:
Raghav Khunger
Date:
2009-01-30 7:59:03 AM
Comment:
This Is A Excellent Control .The Most Beautiful thing i like that everything is without postback. The Sorting Criteria as well as Paging is without postback. U have Given windows programming like feeling.Wow! Excellent.....
|
Title:
RE: Why Should I use this way ?!!
Name:
Mudassar Khan
Date:
2009-01-30 3:54:35 AM
Comment:
Reasons 1. Learn what goes behind the scenes in these controls 2. Customization - I can customize the grid as I want. 3. Also a brief idea on how to do the same using Javascript.
|
Title:
Why Should I use this way ?!!
Name:
.NET Developer
Date:
2009-01-30 3:23:42 AM
Comment:
really nice code, but why should we use that? we already have a gridview and ajax extensions! In visual studio 2008, we have a new DataBound control called "ListView" and "DataPager" will do the work of this articel without the need to write any bit of code!
|
|
|
|