Building a Custom ASP.NET Ajax Enabled Grid Using JavaScript
 
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 StringAs 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 IntegerAs 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 IntegerByVal intPageSet As IntegerAs 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>&nbsp;")
        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("&nbsp;")
        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!






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-19 1:38:58 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search