In this Article, I have described how to build your own Ajax Enabled Grid using JavaScript.

The concept is that using simple HTML table design a Grid like control with Ajax Paging and Sorting functionality without using any ASP.Net Controls.


Connection with Database


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.


<connectionStrings>

    <add name ="conString" connectionString ="Data Source=.\SQLEXPRESS;database=Northwind;AttachDbFileName=|DataDirectory|\NORTHWND.MDF;Integrated Security=true"/>

  </connectionStrings>



Creating HTML Table


Now we will create the structure of the Grid. We will start with the Header of the Grid.

Note I have already created a CSS File you will find the same in the sample code.


<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.


<div id = "dvContent" runat ="server" >

</div>

 


Finally the third and the last part which will act as a Pager for the Grid.


<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.


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

1.     dv – DataView of the Populated Datatable

2.     intPageNo – Current Page Index

3.     intRowsPerPage – Number of Records Per Page


and it Returns the Populated HTML Table as 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.


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.


strResults.Append(" <table id = 'tblContent' cellpadding = '0' cellspacing = '0'  width = '800px'>")

 

The complete function is give below


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 the our HTML Grid

It accepts the following Parameters

1.     intRowCount – Total No of Records in the DataView

2.     intPageNo – Current Page Index

3.     intRowsPerPage – Number of Records Per Page

4.     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.

See in the 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

Firstly 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


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 here I have set the Pages per Set to 10. Hence on the Grid I will see Page Numbers 1 to 10, in next set 11 to 20 and so on.


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.


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>&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


1.     ERROR – default Value False. If Error occurs then value is True


2.     PAGER – contains the HTML for the Pager DIV.


3.     GRID - contains the HTML for the Content DIV.


 


<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 bind to the DIV using the innerHTML property see the code below.


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 its 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 strucure will be broken hence if you place it within these tags the Parser ignores it.


See the code below


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 a 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 that is the object is ready to receive data. The complete function is given below.


 


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 verfies that the request is loaded without any errors.  If everything is fine it calls the ParseXML function which parses the XML Reponse received.


 


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 and then extracts the data using the GetNodeData function


The ERROR Tag has value when some exceptions occurs on server side. If Error Occurs the Error Message is displayed in the Error Label lblError.


 


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 is it 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.


 


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);

}

 


 


For Sorting I have used two dropdowns


1.     For displaying the Fields on which sorting is to be done.


2.     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 making 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 also so that the index value is picked up correctly.


 


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;

    }

}

 

 

 

 

Finally this finishes this Article you can download the code sample attached. It is tested in the following browsers

 

1.     Internet Explorer 7

2.     Firefox 3

3.     Google Chrome

 

AJAXGrid.zip (551.29 kb)