Showing posts with label XML HTTP AJAX. Show all posts
Showing posts with label XML HTTP AJAX. Show all posts

Wednesday, 27 June 2012

Hierarchical GridView in ASP.NET with AJAX, JQuery implementation - Part 2

In previous post, we had seen how to show GridView in hierarchical design such as parent and child relationship. So There will be a parent grid show on the screen when loading the page, to show the details of a particular row the user required to click an expand icon located on the corresponding row. The details of the particular row will show just below the row. The same will repeat for all the rows in the parent Grid.

There are lots of ways to achieve this concept with ASP.NET. I had blogged some of the concept in previous posts. The links are follows –

Hierarchical GridView in ASP.NET
Hierarchical GridView in ASP.NET with AJAX, Javascript implementation
Hierarchical GridView in ASP.NET with AJAX, JQuery implementation - Part 1

In previous post, I had given implementation using JQuery and AJAX concept. As the implementation is using AJAX, it will be very useful when large amount of child records required to be fetched for each row and bind to the grid. So, When clicking the expand icon on the parent Grid, the child Grid View will be created on demand by fetching the records from database and the rest of the records child grids rows are not been fetched from the database.

The problem when using AJAX implementation is the child Grid View will not have any edit functionality on the records in a normal way we do with Grid View. Normally when using a Grid View, some developer gives facility to edit the records with the Grid View itself. So the user can click the Edit link which will make a particular row in edit mode. The user can then provide new data to the rows and save by pressing Save link or cancel the update function using Cancel link. But, when using AJAX implementation for implementing hierarchical Grid View, the child Grid View html source are fetched from another ASPX page using AJAX call and show on the main page inside a DIV. So any server event won’t be able to run with the Child Grid.

To achieve any functionality on the Child Grid such as editing a particular record, we have to use AJAX implementation only. That means, we need to user Java Script or JQuery only to call the server when any event to fire on the server.

I am taking the same example in this post and implementing the following use case.

  1. The page should show a Grid View with list of Orders and Expand icon on the first column.
  2. On click of expand icon on each row, the Order Details of the Order must be fetched from the database and show in the Grid View just below to the row with little adjacent to the first column.
  3. The child Grid View must fetch from the server only on demand to avoid the loading time using AJAX concept.
  4. Once the expand icon clicked, there should be a loading message to inform to the user the details are fetching from the server.
  5. Once the Grid View fetched from the server, the Child Grid should show by hiding the loading message and the expand icon must be changed to collapse icon.
  6. On click of collapse icon, the child grid should hide from the screen.
  7. When the user clicks the Expand icon which was already done before, there should not be a call to the server again for fetching the same details. The system should show the hidden Grid View done in Step 6.
  8. When user moving one page to another page by expanding and collapsing some records and come back to the old pages, the expanded records must be expanded and others are collapsed (means old state must be maintained).
  9. When the child grid shows it should have a column with button, which can be used for editing the particular row.
  10. When the user clicks Edit button, there should be a popup window with list of controls for updating new values. The form should also have Save and Cancel button.
  11. When the user press Save button, the record should be saved to the database and refresh the particular child grid.
  12. When the user press Cancel button, the form should be hidden to continue normal process.

For implementing this requirement, I am using the same code which was posted in last post and enhancing with editing the child Grid View row.

To save the record from the popup screen, there is no .NET server event could be called as the controls are created for the client side (without runat=server). So I am using AJAX concept only for getting the information for a particular record and to save the particular record on the database.

There are multiple ways we can call server method from the client side. Some of the ways are shown in below links.

In this implementation I am planning to use HTTP Handler (GenericHander) method.

The implementation follows –

I have two pages Default.aspx and ChildGridBuilder.aspx.
  1. The Default.aspx page shows the list of Orders on the page. The end user accessing this page only from the browser.
  2. When the user clicking the Expand icon on any of the order row, it will call the ChildGridBuilder.aspx page with Order Id in the query string as parameter using AJAX.
  3. The ChildGridBuilder.aspx page will bind the required records to the GridView and return the HTML script to the Default.aspx page as response to the AJAX call.
  4. The Default.aspx page will show the returned script just below to the Order row expanded inside a DIV control.
The ASPX Script (Default.aspx)
<div id="divHidden">
</div>
<div id="OrderInfoEdit">
    <table style="width:100%">
        <tr>
            <td width="120px">Order Id</td>
            <td><span id="lblOrderId" class="OrderIdInput" /></td>
        </tr>
        <tr>
            <td>Product Name</td>
            <td><span id="lblProductName" class="ProductNameInput" /></td>
        </tr>
        <tr>
            <td>Unit Price</td>
            <td><input id="txtUnitPrice" type="text" style="width:150px" class="UnitPriceInput" /></td>
        </tr>
        <tr>
            <td>Quantity</td>
            <td><input id="txtQuantity" type="text" style="width:150px" class="QuantityInput" /></td>
        </tr>
        <tr>
            <td>Discount</td>
            <td><input id="txtDiscount" type="text" style="width:150px" class="DiscountInput" /></td>
        </tr>
        <tr>
            <td>Amount</td>
            <td><input id="txtAmount" type="text" style="width:150px" class="AmountInput" readonly="readonly" /></td>
        </tr>
        <tr>
            <td colspan="2" style="height:10px">
            <input type="hidden" id="hndProductID" class="ProductIDInput" value="" />
            </td>
        </tr>
        <tr>
            <td colspan="2" style="text-align:center">
                <input type="button" id="btnSave" value="Save" style="width:100px" class="OrderDetailSave" />
                <input type="button" id="btnCancel" class="Cancel" value="Cancel" style="width:100px" />
            </td>
        </tr>
    </table>
</div>
<asp:GridView id="GridViewHierachical" runat="server" AutoGenerateColumns="False"
    Width="100%" DataKeyNames="OrderId" AllowPaging="true" CssClass="GridViewStyle"
    BorderStyle="Solid" PageSize="10" CellPadding="0" CellSpacing="0"
    OnPageIndexChanging="GridViewHierachical_PageIndexChanging" EnableViewState="false">
    <Columns>
        <asp:TemplateField HeaderText="Order ID">
            <ItemStyle Width="10%" />
            <ItemTemplate>
                <input id='hid<%# Eval("OrderID") %>') value='0' type="hidden" />
                <img id="img<%# Eval("OrderID") %>" alt="<%# Eval("OrderID") %>" class="ExpandImage" width="9px" 
                    border="0" src="Images/plus.png" style="cursor:pointer;padding-left:3px;width:12px;height:12px;" />
                <asp:Label ID="lblOrderID" Text='<%# Eval("OrderID") %>' Visible="true" runat="server"></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CustomerID" HeaderText="Customer ID">
            <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShippedDate" HeaderText="Shipped Date"  DataFormatString="{0:MMMM d, yyyy}">
            <ItemStyle Width="20%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipName" HeaderText="Ship Name">
            <ItemStyle Width="20%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipCountry" HeaderText="Ship Country">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipCity" HeaderText="Ship City">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:TemplateField>
            <HeaderStyle CssClass="InvisibleCol" />
            <ItemStyle CssClass="InvisibleCol" />
            <ItemTemplate>
            </td></tr>
                <tr class="ChildRow">
                    <td id="td<%# Eval("OrderID") %>" colspan="6">
                        <div id="div<%# Eval("OrderID") %>" style="width:100%;display:none;">
                            <div style='padding:4px;'>
                                <img src='Images/ajax-loader.gif' alt='' />
                                <span style='color:blue;font-size:17px;font-weight:bold;'>Loading... Please wait</span>
                            </div>
                        </div>
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
    <FooterStyle BackColor="#CCCCCC" ForeColor="Black"></FooterStyle>
    <RowStyle CssClass="GridViewRow" Height="24px"></RowStyle>
    <HeaderStyle CssClass="GridViewHeader" Height="22px"></HeaderStyle>
    <SelectedRowStyle BackColor="#008A8C" Font-Bold="True" ForeColor="White"></SelectedRowStyle>
    <PagerStyle BackColor="#999999" ForeColor="Black" HorizontalAlign="Right"></PagerStyle>
    <AlternatingRowStyle CssClass="GridViewAlternativeRow"></AlternatingRowStyle>
</asp:GridView>
<asp:HiddenField ID="hndExpandedChild" runat="server" Value="" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
I use JQuery version 1.7.2 in this example
<script src="Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
The Code behind Script (Default.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        BindGrid();
    }
}
private void BindGrid()
{
    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
    {
        string strSQL = "SELECT Orders.OrderID, Orders.CustomerID, Orders.ShippedDate, Orders.ShipName, Orders.ShipCountry, Orders.ShipCity FROM Orders";
        SqlCommand command = new SqlCommand(strSQL, connection);
        command.CommandType = CommandType.Text;

        connection.Open();
        SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection);

        IList<Order> orderList = new List<Order>();
        while (dr.Read())
        {
            Order order = new Order();
            order.OrderID = Convert.ToInt32(dr["OrderID"].ToString());
            order.CustomerID = dr["CustomerID"].ToString();
            order.ShippedDate = dr["ShippedDate"].ToString();
            order.ShipName = dr["ShipName"].ToString();
            order.ShipCountry = dr["ShipCountry"].ToString();
            order.ShipCity = dr["ShipCity"].ToString();

            orderList.Add(order);
        }
        GridViewHierachical.DataSource = orderList;
        GridViewHierachical.DataBind();
    }
}

protected void GridViewHierachical_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridViewHierachical.PageIndex = e.NewPageIndex;
    BindGrid();
}

protected void Button1_Click(object sender, EventArgs e)
{

}
The Order entity class (Order.cs)
public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public string ShippedDate { get; set; }
    public string ShipName { get; set; }
    public string ShipCountry { get; set; }
    public string ShipCity { get; set; }
}
The ASPX script (ChildGridBuilder.aspx)
<asp:GridView id="GridViewDetails" runat="server" 
    AllowSorting="false" AutoGenerateColumns="False" Width="100%"
    CellSpacing="1" CellPadding="0" GridLines="Both" DataKeyNames="OrderId"
    BackColor="White" BorderWidth="2px" BorderStyle="Ridge"
    BorderColor="White" AllowPaging="false" ForeColor="#000066">
    <Columns>
        <asp:BoundField DataField="OrderId" HeaderText="Order ID">
            <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ProductName" HeaderText="Product Name">
            <ItemStyle Width="30%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Quantity" HeaderText="Quantity" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Discount" HeaderText="Discount" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Amount" HeaderText="Amount" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:TemplateField HeaderText="Edit">
            <ItemStyle Width="40%" />
            <ItemTemplate>
                <input id="btnOrderID" class="OrderClass" type="button" value="Edit" onclick="ProductEditClient('<%# Eval("OrderID") %>', '<%# Eval("ProductID") %>')" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
    <RowStyle ForeColor="#000066" Height="20px"></RowStyle>
    <SelectedRowStyle BackColor="#669999" Font-Bold="True" ForeColor="White"></SelectedRowStyle>
    <PagerStyle BackColor="White" ForeColor="#000066" HorizontalAlign="Left"></PagerStyle>
    <HeaderStyle CssClass="GridViewHeader"></HeaderStyle>
    <AlternatingRowStyle BorderStyle="Solid" BorderWidth="0px"></AlternatingRowStyle>
</asp:GridView>
The Code behind Script (ChildGridBuilder.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    Response.Clear();
    Response.ContentType = "text/xml";
    BindGrid();

    System.IO.StringWriter stringWrite = new System.IO.StringWriter();
    System.Web.UI.HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWrite);
    GridViewDetails.RenderControl(htmlWrite);
    Response.Write(stringWrite.ToString());
            
    Response.End();
}
public override void VerifyRenderingInServerForm(Control control)
{
}
private void BindGrid()
{
    // I am delaying the response to see the Loading... message
    System.Threading.Thread.Sleep(1000);

    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
    {
        string strSQL = "SELECT OrderDetails.OrderID, OrderDetails.ProductID, Products.ProductName," +
                                      "OrderDetails.UnitPrice, OrderDetails.Quantity, OrderDetails.Discount, " +
                                      "((OrderDetails.UnitPrice * OrderDetails.Quantity) - OrderDetails.Discount) Amount " +
                                            "FROM [Order Details] OrderDetails " +
                                            "JOIN Products ON Products.ProductID = OrderDetails.ProductID " +
                                            "WHERE OrderDetails.OrderID = '" + Request.QueryString["OrderID"].ToString() + "'";
        SqlCommand command = new SqlCommand(strSQL, connection);
        command.CommandType = CommandType.Text;

        connection.Open();
        SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection);

        IList<OrderDetails> detailsList = new List<OrderDetails>();
        while (dr.Read())
        {
            OrderDetails details = new OrderDetails();
            details.OrderID = Convert.ToInt32(dr["OrderID"].ToString());
            details.ProductID = Convert.ToInt32(dr["ProductID"].ToString());
            details.ProductName = dr["ProductName"].ToString();
            details.UnitPrice = Convert.ToDouble(dr["UnitPrice"].ToString());
            details.Quantity = Convert.ToInt32(dr["Quantity"].ToString());
            details.Discount = Convert.ToDouble(dr["Discount"].ToString());
            details.Amount = Convert.ToDouble(dr["Amount"].ToString());

            detailsList.Add(details);
        }

        GridViewDetails.DataSource = detailsList;
        GridViewDetails.DataBind();
    }
}
The OrderDetails entity class (OrderDetails.cs)
public class OrderDetails
{
    public int OrderID { get; set; }
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public double UnitPrice { get; set; }
    public int Quantity { get; set; }
    public double Discount { get; set; }
    public double Amount { get; set; }
}
I use GenericHandler for getting a particular order details to show on the popup screen and to save the same information in the database. So add a GenericHandler (GetOrderData.ashx) file and add the following code.
The GetOrderData.ashx code (GetOrderData.ashx.cs)
public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";

    try
    {
        if (context.Request.Form["Type"] == "GET")
        {
            OrderDetails details = new OrderDetails();
            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
            {
                string strSQL = "select OrderDetails.OrderID, OrderDetails.ProductID, OrderDetails.UnitPrice, OrderDetails.Quantity, OrderDetails.Discount, Products.ProductName, ((OrderDetails.UnitPrice * OrderDetails.Quantity) - OrderDetails.Discount) Amount from [Order Details] OrderDetails JOIN Products ON Products.ProductID = OrderDetails.ProductID Where OrderDetails.OrderID = " + context.Request.Form["OrderID"].ToString() + " And OrderDetails.ProductID = " + context.Request.Form["ProductID"].ToString();
                SqlCommand command = new SqlCommand(strSQL, connection);
                command.CommandType = CommandType.Text;

                connection.Open();
                SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection);

                while (dr.Read())
                {
                    details.OrderID = Convert.ToInt32(dr["OrderID"].ToString());
                    details.ProductID = Convert.ToInt32(dr["ProductID"].ToString());
                    details.ProductName = dr["ProductName"].ToString();
                    details.UnitPrice = Convert.ToDouble(dr["UnitPrice"].ToString());
                    details.Quantity = Convert.ToInt32(dr["Quantity"].ToString());
                    details.Discount = Convert.ToDouble(dr["Discount"].ToString());
                    details.Amount = Convert.ToDouble(dr["Amount"].ToString());
                }
            }
            System.Web.Script.Serialization.JavaScriptSerializer objSerializer =
                    new System.Web.Script.Serialization.JavaScriptSerializer();

            context.Response.Write(objSerializer.Serialize(details));
        }
        else
        {
            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
            {
                string strSQL = "Update [Order Details] Set UnitPrice = '" + context.Request.Form["UnitPrice"].ToString() + "', " +
                                                        " Quantity = '" + context.Request.Form["Quantity"].ToString() + "', " +
                                                        " Discount = '" + context.Request.Form["Discount"].ToString() + "' " +
                                                        " Where OrderID = " + context.Request.Form["OrderID"].ToString() + " " +
                                                        " And ProductID = " + context.Request.Form["ProductID"].ToString();

                SqlCommand command = new SqlCommand(strSQL, connection);

                connection.Open();

                context.Response.Write(command.ExecuteNonQuery());
            }
        }
    }
    catch (Exception ex)
    {
        context.Response.Write("error");
    }
}
The Style Sheet
.GridViewStyle
{
    font-family:Calibri;
    font-size:15px;
}
.GridViewHeader
{
    background: url(../../Images/header.png) repeat-x 0px 0px;
}
.GridViewRow
{
}
.GridViewAlternativeRow
{
    background-color:#edf5ff;
}
.InvisibleCol
{
    display:none;
}
.ChildRow
{
    border-width:0px;
}

/* Style */
#divHidden
{
 width: 100%;
 height:100%;
 background-color:Gray;
 background: url(../../Images/hidden.png) repeat;
 position: absolute;
 top: 0;
 left: 0;
 z-index: 100;
 display: none;
}

.EditInfo
{
    width: 310px;
 height: 220px;
 background-color: White;
 position: absolute;
 left: 50%;
 top: 50%;
 margin-top: -50px;
 margin-left: -168px;
 vertical-align:middle;
 z-index: 1001;
 display: none;
}
#OrderInfoEdit
{
 width: 400px;
 height: 190px;
 background-color: White;
 position: absolute;
 left: 35%;
 top: 35%;
 margin-top: -50px;
 margin-left: -168px;
 vertical-align:middle;
 z-index: 1001;
 display: none;
 border: 1px solid blue;
 margin: 2px;
 padding: 10px;
}
#OrderInfoEdit P
{
 width: 85%;
 margin: 10px auto;
 text-align: left;
}
#OrderInfoEdit P.button
{
 padding-top: 10px;
 border-top: 1px solid #D1D1D1;
}
The JQuery code
$(document).ready(function () {

    if ($('#<%= hndExpandedChild.ClientID %>').val().length > 0) {

        // Will run when some child grids are expanded
        $.each($('#<%= hndExpandedChild.ClientID %>').val().split(","), function (index, value) {
            ExpandCollapse(value);
        });
    }

    $('.ExpandImage').click(function () {

        ExpandCollapse($(this).attr('alt'));

    })
    function ExpandCollapse(orderId) {

        if ($('#div' + orderId)) {

            // Is already ChildGrid shown. If not shown
            if (!isDisplayed($('#div' + orderId))) {

                if ($('#hid' + orderId).val() == '0') {

                    $('#div' + orderId).css("display", "block");
                    $.ajax({
                        url: 'ChildGridBuilder.aspx?datetime=' + new Date().getTime(),
                        type: 'GET',
                        data: "OrderID=" + encodeURIComponent(orderId),
                        dataType: "html",
                        success: function (response) {
                            $('#div' + orderId).html(response);
                            $('#div' + orderId).css("display", "block");
                            $('#img' + orderId).attr('src', 'images/minus.png');
                            $('#hid' + orderId).val("1");
                        },
                        error: function (xhr, ajaxOptions, thrownError) {
                            alert('Error Occured!');
                            $('#div' + orderId).css("display", "none");
                        }
                    });
                }
                else {
                    $('#div' + orderId).css("display", "block");
                    $('#img' + orderId).attr('src', 'images/minus.png');
                }

                var selectedIds = "," + $('#<%= hndExpandedChild.ClientID %>').val() + ",";
                if (selectedIds.indexOf("," + orderId + ",") == -1) {
                    selectedIds += "," + orderId;

                    selectedIds = selectedIds.replace(/,,/g, ","); // Replacing all ,, ,,, ,,,, etc., to ,
                    while (selectedIds.substring(0, 1) == ",") selectedIds = selectedIds.substring(1);

                    $('#<%= hndExpandedChild.ClientID %>').val(selectedIds);
                }
            }
            else { // Already Child Grid Shown
                $('#div' + orderId).css("display", "none");
                $('#img' + orderId).attr('src', 'images/plus.png');

                var selectedIds = "," + $('#<%= hndExpandedChild.ClientID %>').val() + ",";

                if (selectedIds.indexOf("," + orderId + ",") >= 0) {

                    selectedIds = selectedIds.replace("," + orderId + ",", ","); // Removing the Id

                    selectedIds = selectedIds.replace(/,,/g, ","); // Replacing all ,, ,,, ,,,, etc., to ,
                    selectedIds = selectedIds.substr(1, selectedIds.length - 2); // Removing , both the end and assigning

                    $('#<%= hndExpandedChild.ClientID %>').val(selectedIds);
                }
            }
        }
    }
    ProductEditClient = function (orderId, productId) {
        $("#divHidden").css("display", "block");
        $("#OrderInfoEdit").css("display", "block");

        jQuery.post('GetOrderData.ashx',
                    { Type: "GET", OrderID: orderId, ProductID: productId },
                    function (data) {
                        var orderDetails = eval("(" + data + ")");
                        $(".OrderIdInput").text(orderDetails["OrderID"]);
                        $(".ProductNameInput").text(orderDetails["ProductName"]);
                        $(".UnitPriceInput").val(orderDetails["UnitPrice"]);
                        $(".QuantityInput").val(orderDetails["Quantity"]);
                        $(".DiscountInput").val(orderDetails["Discount"]);
                        $(".AmountInput").val(orderDetails["Amount"]);
                        $(".ProductIDInput").val(orderDetails["ProductID"]);
                    })
                    .error(function () {
                        alert('Error Occured!');
                        $('#div' + orderId).css("display", "none");
                    })
    }
    // This function is for cancel button, when click of this button the popup screen will be hidden and show as normal.
    $(".Cancel").click(function () {
        $("#divHidden").css("display", "none");
        $("#OrderInfoEdit").css("display", "none");
    });
    $(".UnitPriceInput").blur(function() {
        $(".AmountInput").val(($(".UnitPriceInput").val() * $(".QuantityInput").val()) - $(".DiscountInput").val());
    });
    $(".QuantityInput").blur(function() {
        $(".AmountInput").val(($(".UnitPriceInput").val() * $(".QuantityInput").val()) - $(".DiscountInput").val());
    });
    $(".DiscountInput").blur(function() {
        $(".AmountInput").val(($(".UnitPriceInput").val() * $(".QuantityInput").val()) - $(".DiscountInput").val());
    });
    $(".OrderDetailSave").click(function () {
        jQuery.post('GetOrderData.ashx',
                    { Type: "SAVE", OrderId: $(".OrderIdInput").text(),
                        ProductID: $(".ProductIDInput").val(),
                        UnitPrice: $(".UnitPriceInput").val(),
                        Quantity: $(".QuantityInput").val(),
                        Discount: $(".DiscountInput").val(),
                        Amount: $(".AmountInput").val(),
                    },
                    function (data) {
                        if (data == 1)
                        {
                            alert("Record Saved Successfully");
                            ExpandCollapse($(".OrderIdInput").text());
                            $('#hid' + $(".OrderIdInput").text()).val('0');
                            ExpandCollapse($(".OrderIdInput").text());
                            $("#divHidden").css("display", "none");
                            $("#OrderInfoEdit").css("display", "none");
                        }
                    })
                    .error(function () {
                        alert('Error Occured!');
                        $('#div' + orderId).css("display", "none");
                    })
    });
    function isDisplayed(object) {
        // if the object is visible return true
        if ($(object).css('display') == 'block') {
            return true;
        }
        // if the object is not visible return false
        return false;
    };
});
Note: When saving the child grid details on the popup, it calls the ExpandCollapse to fetch the new details and overwrite the existing content. Because the AJAX calls are caching the data, that will return the same content. So I have added current time in line # 27 with the url.

The output of the code would be -







download the working example of the source code in C# here and in VB here.

Thursday, 21 June 2012

Hierarchical GridView in ASP.NET with AJAX, JQuery implementation - Part 1

Previously I had blogged some post related to showing two grids in hierarchical way. For example - when I need to show an invoice reports on the screen, the user requesting to show the list of invoices at initial time and they can drill down the details of item details of each invoice by clicking on respective row.

The implementations can be in multiple ways, but I had blogged the implementation with AJAX and without AJAX using Java script on ASP.NET page. Below are the URLs of the same –

Hierarchical GridView in ASP.NET
Hierarchical GridView in ASP.NET with AJAX, Javascript implementation

In this post I am taking the same requirements done before and enhance with some additional functionality as per readers expectations.

The requirement on this implementation will be –
  1. The page should show a Grid View with list of Orders with Expand icon on the first column.
  2. On click of expand icon on each row, the Order Details of the Order must be fetched from the database and show in the Grid View just below to the row with little adjacent to the first column.
  3. The child Grid View must fetch from the server only on demand to avoid the loading time using AJAX concept.
  4. Once the expand icon clicked, there should be a loading message to inform to the user the details are fetching from the server.
  5. Once the Grid View fetched from the server, the Child Grid should show by hiding the loading message and the expand icon must be changed to collapse icon.
  6. On click of collapse icon, the child grid should hide from the screen.
  7. When the user clicks the Expand icon which was already done before, there should not be a call to the server again for fetching the same details. The system should show the hidden Grid View done in Step 6.
  8. When user moving one page to another page by expanding and collapsing some records and come back to the old pages, the expanded records must be expanded and others are collapsed (means old state must be maintained).
As stated, all requirements are same as in previous posts except the eighth point which is to maintain the state of the previous pages.

The implementation done in JQuery on ASP.NET page with AJAX concept.

Note: The implementation done here using AJAX concept, so it won’t provide the ability to update, delete records on child grid level. The next post which is continuation of this implementation explains the updation, deletion of record on child grid records.

The implementation follows –

I have two pages Default.aspx and ChildGridBuilder.aspx.
  1. The Default.aspx page shows the list of Orders on the page. The end user accessing this page only from the browser.
  2. When the user clicking the Expand icon on any of the order row, it will call the ChildGridBuilder.aspx page with Order Id in the query string as parameter using AJAX.
  3. The ChildGridBuilder.aspx page will bind the required records to the GridView and return the HTML script to the Default.aspx page as response to the AJAX call.
  4. The Default.aspx page will show the returned script just below to the Order row expanded inside a DIV control.
I am using JQuery script file version 1.7.2 (jquery-1.7.2.min.js) for this implementation.
The ASPX Script (Default.aspx)
<asp:GridView id="GridViewHierachical" runat="server" AutoGenerateColumns="False"
    Width="100%" DataKeyNames="OrderId" AllowPaging="true" CssClass="GridViewStyle"
    BorderStyle="Solid" PageSize="10" CellPadding="0" CellSpacing="0"
    OnPageIndexChanging="GridViewHierachical_PageIndexChanging" EnableViewState="false">
    <Columns>
        <asp:TemplateField HeaderText="Order ID">
            <ItemStyle Width="10%" />
            <ItemTemplate>
                <input id='hid<%# Eval("OrderID") %>') value='0' type="hidden" />
                <img id="img<%# Eval("OrderID") %>" alt="<%# Eval("OrderID") %>" class="ExpandImage" width="9px" 
                    border="0" src="Images/plus.png" style="cursor:pointer;padding-left:3px;width:12px;height:12px;" />
                <asp:Label ID="lblOrderID" Text='<%# Eval("OrderID") %>' Visible="true" runat="server"></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CustomerID" HeaderText="Customer ID">
            <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShippedDate" HeaderText="Shipped Date"  DataFormatString="{0:MMMM d, yyyy}">
            <ItemStyle Width="20%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipName" HeaderText="Ship Name">
            <ItemStyle Width="20%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipCountry" HeaderText="Ship Country">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="ShipCity" HeaderText="Ship City">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:TemplateField>
            <HeaderStyle CssClass="InvisibleCol" />
            <ItemStyle CssClass="InvisibleCol" />
            <ItemTemplate>
            </td></tr>
                <tr class="ChildRow">
                    <td id="td<%# Eval("OrderID") %>" colspan="6">
                        <div id="div<%# Eval("OrderID") %>" style="width:100%;display:none;">
                            <div style='padding:4px;'>
                                <img src='Images/ajax-loader.gif' alt='' />
                                <span style='color:blue;font-size:17px;font-weight:bold;'>Loading... Please wait</span>
                            </div>
                        </div>
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
    <FooterStyle BackColor="#CCCCCC" ForeColor="Black"></FooterStyle>
    <RowStyle CssClass="GridViewRow" Height="24px"></RowStyle>
    <HeaderStyle CssClass="GridViewHeader" Height="22px"></HeaderStyle>
    <SelectedRowStyle BackColor="#008A8C" Font-Bold="True" ForeColor="White"></SelectedRowStyle>
    <PagerStyle BackColor="#999999" ForeColor="Black" HorizontalAlign="Right"></PagerStyle>
    <AlternatingRowStyle CssClass="GridViewAlternativeRow"></AlternatingRowStyle>
</asp:GridView>
<asp:HiddenField ID="hndExpandedChild" runat="server" Value="" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
The Code behind Script (Default.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        BindGrid();
    }
}
private void BindGrid()
{
    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
    {
        string strSQL = "SELECT Orders.OrderID, Orders.CustomerID, Orders.ShippedDate, Orders.ShipName, Orders.ShipCountry, Orders.ShipCity FROM Orders";
        SqlCommand command = new SqlCommand(strSQL, connection);
        command.CommandType = CommandType.Text;

        connection.Open();
        SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection);

        IList<Order> orderList = new List<Order>();
        while (dr.Read())
        {
            Order order = new Order();
            order.OrderID = Convert.ToInt32(dr["OrderID"].ToString());
            order.CustomerID = dr["CustomerID"].ToString();
            order.ShippedDate = dr["ShippedDate"].ToString();
            order.ShipName = dr["ShipName"].ToString();
            order.ShipCountry = dr["ShipCountry"].ToString();
            order.ShipCity = dr["ShipCity"].ToString();

            orderList.Add(order);
        }
        GridViewHierachical.DataSource = orderList;
        GridViewHierachical.DataBind();
    }
}

protected void GridViewHierachical_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridViewHierachical.PageIndex = e.NewPageIndex;
    BindGrid();
}

protected void Button1_Click(object sender, EventArgs e)
{

}
public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public string ShippedDate { get; set; }
    public string ShipName { get; set; }
    public string ShipCountry { get; set; }
    public string ShipCity { get; set; }
}
Included the Jquery file in the script folder and add the script in the Default.aspx file.
<script src="Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
The ASPX Script (ChildGridBuilder.aspx)
<asp:GridView id="GridViewDetails" runat="server" 
    AllowSorting="false" AutoGenerateColumns="False" Width="100%"
    CellSpacing="1" CellPadding="0" GridLines="Both" DataKeyNames="OrderId"
    BackColor="White" BorderWidth="2px" BorderStyle="Ridge"
    BorderColor="White" AllowPaging="false" ForeColor="#000066">
    <Columns>
        <asp:BoundField DataField="OrderId" HeaderText="Order ID">
            <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ProductName" HeaderText="Product Name">
            <ItemStyle Width="30%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Quantity" HeaderText="Quantity" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Discount" HeaderText="Discount" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
        <asp:BoundField DataField="Amount" HeaderText="Amount" DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right">
            <ItemStyle Width="15%" />
        </asp:BoundField>
    </Columns>
    <RowStyle ForeColor="#000066" Height="20px"></RowStyle>
    <SelectedRowStyle BackColor="#669999" Font-Bold="True" ForeColor="White"></SelectedRowStyle>
    <PagerStyle BackColor="White" ForeColor="#000066" HorizontalAlign="Left"></PagerStyle>
    <HeaderStyle CssClass="GridViewHeader"></HeaderStyle>
    <AlternatingRowStyle BorderStyle="Solid" BorderWidth="0px"></AlternatingRowStyle>
</asp:GridView>
The Code Behind Code (ChildGridBuilder.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    Response.Clear();
    Response.ContentType = "text/xml";
    BindGrid();

    System.IO.StringWriter stringWrite = new System.IO.StringWriter();
    System.Web.UI.HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWrite);
    GridViewDetails.RenderControl(htmlWrite);
    Response.Write(stringWrite.ToString());
            
    Response.End();
}
public override void VerifyRenderingInServerForm(Control control)
{
}
private void BindGrid()
{
    // I am delaying the response to see the Loading... message
    System.Threading.Thread.Sleep(1000);

    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLConnection"].ConnectionString))
    {
        string strSQL = "SELECT OrderDetails.OrderID, OrderDetails.ProductID, Products.ProductName," +
                                      "OrderDetails.UnitPrice, OrderDetails.Quantity, OrderDetails.Discount, " +
                                      "((OrderDetails.UnitPrice * OrderDetails.Quantity) - OrderDetails.Discount) Amount " +
                                            "FROM [Order Details] OrderDetails " +
                                            "JOIN Products ON Products.ProductID = OrderDetails.ProductID " +
                                            "WHERE OrderDetails.OrderID = '" + Request.QueryString["OrderID"].ToString() + "'";
        SqlCommand command = new SqlCommand(strSQL, connection);
        command.CommandType = CommandType.Text;

        connection.Open();
        SqlDataReader dr = command.ExecuteReader(CommandBehavior.CloseConnection);

        IList<OrderDetails> detailsList = new List<OrderDetails>();
        while (dr.Read())
        {
            OrderDetails details = new OrderDetails();
            details.OrderID = Convert.ToInt32(dr["OrderID"].ToString());
            details.ProductID = Convert.ToInt32(dr["ProductID"].ToString());
            details.ProductName = dr["ProductName"].ToString();
            details.UnitPrice = Convert.ToDouble(dr["UnitPrice"].ToString());
            details.Quantity = Convert.ToInt32(dr["Quantity"].ToString());
            details.Discount = Convert.ToDouble(dr["Discount"].ToString());
            details.Amount = Convert.ToDouble(dr["Amount"].ToString());

            detailsList.Add(details);
        }

        GridViewDetails.DataSource = detailsList;
        GridViewDetails.DataBind();
    }
}
public class OrderDetails
{
    public int OrderID { get; set; }
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public double UnitPrice { get; set; }
    public int Quantity { get; set; }
    public double Discount { get; set; }
    public double Amount { get; set; }
}
The Style Sheet Script
.GridViewStyle
{
    font-family:Calibri;
    font-size:15px;
}
.GridViewHeader
{
    background: url(../../Images/header.png) repeat-x 0px 0px;
}
.GridViewRow
{
}
.GridViewAlternativeRow
{
    background-color:#edf5ff;
}
.InvisibleCol
{
    display:none;
}
.ChildRow
{
    border-width:0px;
}

/* Style */
#divHidden
{
 width: 100%;
 height:100%;
 background-color:Gray;
 background: url(../Images/hidden.png) repeat;
 position: absolute;
 top: 0;
 left: 0;
 z-index: 100;
 display: none;
}

.EditInfo
{
    width: 310px;
 height: 220px;
 background-color: White;
 position: absolute;
 left: 50%;
 top: 50%;
 margin-top: -50px;
 margin-left: -168px;
 vertical-align:middle;
 z-index: 1001;
 display: none;
}
The JQuery Script
$(document).ready(function () {

    if ($('#<%= hndExpandedChild.ClientID %>').val().length > 0) {

        // Will run when some child grids are expanded
        $.each($('#<%= hndExpandedChild.ClientID %>').val().split(","), function (index, value) {
            ExpandCollapse(value);
        });
    }

    $('.ExpandImage').click(function () {

        ExpandCollapse($(this).attr('alt'));

    })
    function ExpandCollapse(orderId) {
        if ($('#div' + orderId)) {

            // Is already ChildGrid shown. If not shown
            if (!isDisplayed($('#div' + orderId))) {

                if ($('#hid' + orderId).val() == '0') {

                    $('#div' + orderId).css("display", "block");

                    $.ajax({
                        url: 'ChildGridBuilder.aspx',
                        type: 'GET',
                        data: "OrderID=" + encodeURIComponent(orderId),
                        dataType: "html",
                        success: function (response) {
                            $('#div' + orderId).html(response);
                            $('#div' + orderId).css("display", "block");
                            $('#img' + orderId).attr('src', 'images/minus.png');
                            $('#hid' + orderId).val("1");
                        },
                        error: function (xhr, ajaxOptions, thrownError) {
                            alert('Error Occured!');
                            $('#div' + orderId).css("display", "none");
                        }
                    });
                }
                else {
                    $('#div' + orderId).css("display", "block");
                    $('#img' + orderId).attr('src', 'images/minus.png');
                }

                var selectedIds = "," + $('#<%= hndExpandedChild.ClientID %>').val() + ",";
                if (selectedIds.indexOf("," + orderId + ",") == -1) {
                    selectedIds += "," + orderId;

                    selectedIds = selectedIds.replace(/,,/g, ","); // Replacing all ,, ,,, ,,,, etc., to ,
                    while (selectedIds.substring(0, 1) == ",") selectedIds = selectedIds.substring(1);

                    $('#<%= hndExpandedChild.ClientID %>').val(selectedIds);

                    //alert(orderId + "\n" + $('#<%= hndExpandedChild.ClientID %>').val());
                }
            }
            else { // Already Child Grid Shown
                $('#div' + orderId).css("display", "none");
                $('#img' + orderId).attr('src', 'images/plus.png');

                //alert($('#<%= hndExpandedChild.ClientID %>').val());

                var selectedIds = "," + $('#<%= hndExpandedChild.ClientID %>').val() + ",";
                //alert(selectedIds.indexOf("," + orderId + ","));

                if (selectedIds.indexOf("," + orderId + ",") >= 0) {

                    selectedIds = selectedIds.replace("," + orderId + ",", ","); // Removing the Id

                    selectedIds = selectedIds.replace(/,,/g, ","); // Replacing all ,, ,,, ,,,, etc., to ,
                    selectedIds = selectedIds.substr(1, selectedIds.length - 2); // Removing , both the end and assigning

                    //alert(selectedIds);
                    $('#<%= hndExpandedChild.ClientID %>').val(selectedIds);

                    //alert(orderId + "\n" + $('#<%= hndExpandedChild.ClientID %>').val());
                }
            }
        }
    }
    function isDisplayed(object) {
        // if the object is visible return true
        if ($(object).css('display') == 'block') {
            return true;
        }
        // if the object is not visible return false
        return false;
    };
});
In this implementation, I am using an hidden control for storing the list of Order Ids (in comma separated) which are expanded in all pages. So when expanding a row, it will store the Order Id to the hidden control value and when collapse - it will delete from the same. When use navigating to the old page in the GridView, the Javascript search list of orders present on the particular page and search on the hidden control value and expand accordingly.

The output of this code would be as below -


download the working example of the source code in C# here and in VB here.

This implementation is not helpful for editing a particular row on the Child GridView. This is because the child GridView build in another page (ChildGridBuilder.aspx) and shown in Default.aspx using AJAX concept. So it wont have code behind code to execute.

The next post will explain how to achieve this requirement.

Sunday, 20 May 2012

Rendering GridView Rows when scrolling down using AJAX concept - Part 2

In previous post, We had seen how to render the GridView rows from the server and show while scrolling the GridView.

This is a continuation of that post which discuss the second implementation. I had explained the background of this implementation in the first post. So, It is better to read the post for understanding how it is been implemented (but there is no dependency in the code).

Second Implementation
In the previous implementation, the new records are fetched and adds at the end of the GridView while scrolling down to the end of the GridView. In this implementation we will discuss how to show a progress message at the top corner of the screen when the server is fetching the records. So the user can get to know the server is under processing and has to wait for the response.

The use case will be the same as the first method additionally there will be a message for each server calls from the Javascript using AJAX. So the use case would be
  1. Show list of records in a GridView with all sort of customizations possible (such as colors, data formatting etc.,).
  2. The GridView should show all the records in a single page without having pagination.
  3. The GridView should have a scroll bar to scroll and see the records up and down.
  4. Initially the GridView should show only first two pages on the screen (or decided by the programmer on the code).
  5. When user scrolls down to see last records, the page should fetch the next 10 records (or decided by programmer in the code) and add with the records at the last shown in the GridView.
  6. When user scrolls further down to see the newly added records, the page should fetch the next 10 records and add at the last to the GridView. This repeats until last record reached.
  7. When the server is fetching the records from the database, the page should show a process message such as "Working on your Request" on the top-right corner. Once the records are fetched and shown at the end of the GridView, the message should hidden.
I have given two example for this implementation,
  1. Without sorting option on the GridView
  2. With Sorting on the GridView.

Implementation without Sorting in GridView

The ASPX script (ProcessMessage.aspx)
<div id="GridViewContainer" style="width:100%;height:400px;" onscroll="RefreshRows(this)" >
    <asp:GridView ID="grdViewProducts" CssClass="ViewProducts" runat="server" AutoGenerateColumns="False" TabIndex="1" 
        AllowPaging="false" DataKeyNames="ProductID" Width="100%" GridLines="Both" CellPadding="3" 
        AllowSorting="false" BackColor="White">
        <Columns>
            <asp:BoundField DataField="ProductName" HeaderText="Product Name" 
                SortExpression="ProductName">
            <ItemStyle Width="26%" />
            </asp:BoundField>
            <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                SortExpression="CategoryName" >
            <ItemStyle Width="17%" />
            </asp:BoundField>
            <asp:BoundField DataField="QuantityPerUnit" HeaderText="Quantity/Unit" 
                SortExpression="QuantityPerUnit" >
            <ItemStyle Width="17%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" 
                SortExpression="UnitPrice" DataFormatString="{0:#0.00}" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" 
                SortExpression="UnitsInStock" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" 
                SortExpression="UnitsOnOrder" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level" 
                SortExpression="ReorderLevel" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
        </Columns>
        <EmptyDataTemplate>
            <div>
                <p style="color:Red">No search results found.</p>
            </div>
        </EmptyDataTemplate>
        <PagerSettings Position="Bottom" />
        <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
        <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
        <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
        <SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
        <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
        <AlternatingRowStyle BackColor="#F7F7F7" />
        <SortedAscendingCellStyle BackColor="#F4F4FD" />
        <SortedAscendingHeaderStyle BackColor="#5A4C9D" />
        <SortedDescendingCellStyle BackColor="#D8D8F0" />
        <SortedDescendingHeaderStyle BackColor="#3E3277" />
    </asp:GridView>
    <div id="divChildGrid">
    </div>
</div>
<input id="hiddenInitialNoOfPageLoad" type="hidden" value="0" runat="server" />
<input id="hiddenTotalRecords" type="hidden" value="0" runat="server" />
<input id="hiddenPageRecordCount" type="hidden" value="0" runat="server" />
The C# Code behind (ProcessMessage.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        hiddenInitialNoOfPageLoad.Value = "2";
        hiddenPageRecordCount.Value = ConfigurationManager.AppSettings["PageRecordCount"].ToString();
        BindGrid();
    }
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(hiddenPageRecordCount.Value) * 2; // I am fetching 3 pages at first time

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(1, intPageRecordCount, string.Empty, SortType.Ascending, out totalRecordCount); // Getting First three pages
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdViewProducts.DataSource = ProductViewList;
    grdViewProducts.DataBind();

    // Assign the Total Record count to 
    hiddenTotalRecords.Value = totalRecordCount.ToString();
}
The Javascript (ProcessMessage.aspx)
var is_ie = (navigator.userAgent.indexOf('MSIE') >= 0) ? 1 : 0;
var is_ie5 = (navigator.appVersion.indexOf("MSIE 5.5") != -1) ? 1 : 0;
var xmlHttp;
var varStartAJAXPage = 3;
var varPageOnBehind = 0;

function pageLoad() {
    GetChildGrid(varStartAJAXPage);
    ShowProgress('Hide');
}
window.onload = pageLoad;

/* This function requests the HTTPRequest, will be used to render the Dynamic content html markup 
* and it will call HandleResponse to handle the response
*/
function GetChildGrid(pageId) {

    // Get the Div
    var childGridDiv = document.getElementById("divChildGrid");

    if (childGridDiv) {
        // If the Child Grid is not fetched, then go and fetch it.
        var varTotalRecords = document.getElementById('<%= hiddenTotalRecords.ClientID %>').value;
        var varPageRecordCount = document.getElementById('<%= hiddenPageRecordCount.ClientID %>').value;

        if ((pageId > 0) && (parseInt(varTotalRecords) > (parseInt(varPageRecordCount) * (parseInt(pageId) - 1)))) {

            var url = 'RowsBuilder.aspx?PageId=' + pageId +
                        '&PageRecordsCount=' + varPageRecordCount;
            xmlHttp = createAjaxObject();
            if (xmlHttp) {
                xmlHttp.open('get', url, true);
                xmlHttp.onreadystatechange = function () {
                    HandleResponse(pageId);
                }
                varPageOnBehind = varStartAJAXPage;
                xmlHttp.send(null);
            }
        }
    }
}
function RefreshRows(div) {
    if (varStartAJAXPage - varPageOnBehind > 0) {
        if ((div.scrollHeight - (div.scrollTop + div.offsetHeight)) < 200) {
            varPageOnBehind = varStartAJAXPage;
            GetChildGrid(varStartAJAXPage);
        }
    }
}
/* This function is used to handler the http response */
function HandleResponse(pageId) {

    var childGridDiv = document.getElementById("divChildGrid");

    // If Response completed
    if (xmlHttp.readyState == 4) {

        if (xmlHttp.status == 200) {

            // Here is the response
            var str = xmlHttp.responseText;

            var divRows = document.createElement('div');
            divRows.innerHTML = str;

            childGridDiv.appendChild(divRows);
            varStartAJAXPage++;

            xmlHttp.abort();

            ShowProgress('Hide');
        }
        else {
            ShowProgress('Show');
        }
    }
    else {
        ShowProgress('Show');
    }
}

function ShowProgress(ShowFlag)
{
    if (ShowFlag == 'Show')
    {
        var progressDiv;
        if (!document.getElementById('divProgress'))
        {
                    
            progressDiv = document.createElement("div");
            progressDiv.setAttribute("id", 'divProgress');
            progressDiv.className = "ProgressStyle";
            
            var lblProcessMessage = document.createTextNode("Working on your Request");

            progressDiv.appendChild(lblProcessMessage);
            
            progressDiv.style.left = (screen.width - 160) + "px";
            progressDiv.style.top = "3px";
            progressDiv.style.position = "absolute";
            progressDiv.style.zIndex = 10000;
            document.body.appendChild(progressDiv);
            
        }
        else
            progressDiv = document.getElementById('divProgress');
            
        var scrollpos = getScrollingPosition();
        progressDiv.style.left = (screen.width - 265) + "px";
        progressDiv.style.top = scrollpos[1] + "px";
        progressDiv.style.position = "absolute";
        progressDiv.style.zIndex = 2;
        progressDiv.style.visibility = "visible";
        
    }
    if (ShowFlag == 'Hide')
    {
        progressDiv = document.getElementById('divProgress');
        progressDiv.style.visibility = "hidden";
    }
}
function getScrollingPosition() {
    var position = [0, 0];
    if (typeof window.pageYOffset != 'undefined') {
        position = [window.pageXOffset, window.pageYOffset];
    }
    else
        if (typeof document.documentElement.scrollTop != 'undefined' &&
            document.documentElement.scrollTop > 0
    ) {
            position = [document.documentElement.scrollLeft,
                    document.documentElement.scrollTop
                    ];
        }
        else
            if (typeof document.body.scrollTop != 'undefined') {
                position = [document.body.scrollLeft, document.body.scrollTop];
            }
    return position;
}
/* function to create Ajax object */
function createAjaxObject() {
    var ro;
    var browser = navigator.appName;
    if (browser == "Microsoft Internet Explorer") {
        if (xmlHttp != null) {
            xmlHttp.abort();
        }
        ro = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else {
        if (xmlHttp != null) {
            xmlHttp.abort();
        }
        ro = new XMLHttpRequest();
    }
    return ro;
}

/* Get the XML Http Object */
function GetXmlHttpObject(handler) {
    var objXmlHttp = null;
    if (is_ie) {
        var strObjName = (is_ie5) ? 'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP';

        try {
            objXmlHttp = new ActiveXObject(strObjName);
            objXmlHttp.onreadystatechange = handler;
        }
        catch (e) {
            alert('Object could not be created');
            return;
        }
    }
    return objXmlHttp;
}

function xmlHttp_Get(xmlhttp, url) {
    xmlhttp.open('GET', url, true);
    xmlhttp.send(null);
}
The CSS Stylesheet (ProcessMessage.aspx)
#GridViewContainer
{           
    overflow: auto;
}
.ViewProducts
{
    border-top-style:solid;
    border-left-style:solid;
    border-right-style:solid;
    border-bottom-style:none;
}
.ViewChildProducts
{
    border-top-style:solid;
    border-left-style:solid;
    border-right-style:solid;
    border-bottom-style:none;
}
.ProgressStyle
{
    width:170px;
    padding-left:10px;
    height:20px;
    color:Black;
    background-color:#ffe0b3;
    border:gray 0px solid;
}
The Respondent ASPX script (RowsBuilder.aspx)
<asp:GridView ID="grdAJAXViewProducts" runat="server" ShowHeader="false" ShowFooter="false"
    AutoGenerateColumns="False" TabIndex="1" AllowPaging="false" DataKeyNames="ProductID"
    Width="100%" GridLines="Both" CellPadding="3" AllowSorting="True"
    BackColor="White" CssClass="ViewProducts">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product Name" 
            SortExpression="ProductName" >
        <ItemStyle Width="26%" />
        </asp:BoundField>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" >
        <ItemStyle Width="17%" />
        </asp:BoundField>
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Quantity/Unit" 
            SortExpression="QuantityPerUnit" >
        <ItemStyle Width="17%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" 
            SortExpression="UnitPrice" DataFormatString="{0:#0.00}" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" 
            SortExpression="UnitsInStock" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" 
            SortExpression="UnitsOnOrder" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level" 
            SortExpression="ReorderLevel" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
    </Columns>
    <PagerSettings Position="Bottom" />
    <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
    <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
    <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
    <SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
    <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
    <AlternatingRowStyle BackColor="#F7F7F7" />
    <SortedAscendingCellStyle BackColor="#F4F4FD" />
    <SortedAscendingHeaderStyle BackColor="#5A4C9D" />
    <SortedDescendingCellStyle BackColor="#D8D8F0" />
    <SortedDescendingHeaderStyle BackColor="#3E3277" />
</asp:GridView>
The Code behind file of Respondent (RowsBuilder.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if ((!Page.IsPostBack) && (Request.QueryString["PageId"] != null))
    {
        Response.Clear();
        Response.ContentType = "text/xml";

        BindGrid();

        System.IO.StringWriter stringWrite = new System.IO.StringWriter();
        System.Web.UI.HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWrite);
        grdAJAXViewProducts.RenderControl(htmlWrite);
        Response.Write(stringWrite.ToString());

        Response.End();
    }
}
public override void VerifyRenderingInServerForm(Control control)
{
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;
            
    string strSortExpression = string.Empty;
    SortType sortType = SortType.Ascending;

    if (Request.QueryString["SortExpression"] != null)
    {
        // Get the SortByExpression and SortType from Query String (by default, that will be updated in script)
        strSortExpression = Request.QueryString["SortExpression"].ToString().Split(",".ToCharArray())[0];
        sortType = (SortType)Enum.Parse(typeof(SortType), Request.QueryString["SortExpression"].ToString().Split(",".ToCharArray())[1], true);
    }

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(Request.QueryString["PageRecordsCount"].ToString());

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(Convert.ToInt32(Request.QueryString["PageId"].ToString()), intPageRecordCount, strSortExpression, sortType, out totalRecordCount);
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdAJAXViewProducts.DataSource = ProductViewList;
    grdAJAXViewProducts.DataBind();
}
The Class for fetching the Data from Database (ProductDAO.cs)
public class ProductDAO
{
    public IList<ProductView> GetProducts(int currentPageNo, int pageRecordsCount, string sortBy, SortType sortType, out int totalRecordCount)
    {
        SqlConnection connection = null;
        SqlDataReader dataReader = null;

        try
        {
            IList<ProductView> productViewList = new List<ProductView>();
            connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString());

            SqlDataAdapter dataAdaptor = new SqlDataAdapter("SP_GET_PRODUCTS", connection);
            dataAdaptor.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@CurrentPageNo", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@CurrentPageNo"].Value = currentPageNo;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@PageRecordsCount", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@PageRecordsCount"].Value = pageRecordsCount;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@SortBy", SqlDbType.VarChar));
            dataAdaptor.SelectCommand.Parameters["@SortBy"].Value = sortBy;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@SortType", SqlDbType.VarChar));
            dataAdaptor.SelectCommand.Parameters["@SortType"].Value = ((sortType == null) ? string.Empty : (sortType == SortType.Ascending ? "Asc" : "Desc"));

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@TotalRecordCount", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@TotalRecordCount"].Direction = ParameterDirection.Output;
            connection.Open();

            DataSet dataSet = new DataSet();
            dataAdaptor.Fill(dataSet);

            foreach (DataRow dataRow in dataSet.Tables[0].Rows)
            {
                ProductView productView = new ProductView();
                productView.ProductID = Convert.ToInt32(dataRow["ProductID"].ToString());
                productView.ProductName = dataRow["ProductName"].ToString();
                productView.CompanyName = dataRow["CompanyName"].ToString();
                productView.CategoryName = dataRow["CategoryName"].ToString();
                productView.QuantityPerUnit = dataRow["QuantityPerUnit"].ToString();
                productView.UnitPrice = Convert.ToDouble(dataRow["UnitPrice"].ToString());
                productView.UnitsInStock = Convert.ToInt32(dataRow["UnitsInStock"].ToString());
                productView.UnitsOnOrder = Convert.ToInt32(dataRow["UnitsOnOrder"].ToString());
                productView.ReorderLevel = Convert.ToInt32(dataRow["ReorderLevel"].ToString());

                productViewList.Add(productView);
            }
            // To make more processing time 
            //for (int i = 0; i < (1000000000/2); i++) { int b = 5 + 4 + 3 + 2 - 3; }
            totalRecordCount = Convert.ToInt32(dataAdaptor.SelectCommand.Parameters["@TotalRecordCount"].Value);
            connection.Close();

            //return
            return productViewList;
        }
        catch (Exception ex)
        {
            if (connection != null) connection.Close();
            if (dataReader != null) dataReader.Close();

            // Log
            throw ex;
        }
    }
}
The ProductView entity class
public class ProductView
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string CompanyName { get; set; }
    public string CategoryName { get; set; }
    public string QuantityPerUnit { get; set; }
    public double UnitPrice { get; set; }
    public int UnitsInStock { get; set; }
    public int UnitsOnOrder { get; set; }
    public int ReorderLevel { get; set; }
}
public enum SortType
{
    Ascending,
    Descending
}
The Database script
Create View [dbo].[ProductView]
As
Select Products.ProductID,
  Products.ProductName,
  Suppliers.CompanyName,
  Categories.CategoryName,
  Products.QuantityPerUnit,
  Products.UnitPrice,
  Products.UnitsInStock,
  Products.UnitsOnOrder,
  Products.ReorderLevel
from Products
Join Suppliers on Suppliers.SupplierID = Products.SupplierID
Join Categories on Categories.CategoryID = Products.CategoryID
Go


CREATE PROCEDURE [dbo].[SP_GET_PRODUCTS]
@CurrentPageNo AS INT,
@PageRecordsCount AS INT,
@SortBy AS NVARCHAR(250),
@SortType AS NVARCHAR(250),
@TotalRecordCount AS INT OUTPUT
AS
 
SET NOCOUNT ON
 
DECLARE @SQL AS NVARCHAR(1000)
DECLARE @RecordsFrom AS INT
DECLARE @RecordsTo AS INT
 
-- RecordsFrom => For Ex: CurrentPage 2, PageRecordCount = 10, then (((2-1) * 10) + 1) => ((1 * 10) + 1) => 11
SET @RecordsFrom = (((@CurrentPageNo - 1) * @PageRecordsCount) + 1) 
 
-- @RecordsTo => For Ex: CurrentPage 2, PageRecordCount = 10, then (2 * 10) => 20
SET @RecordsTo = (@CurrentPageNo * @PageRecordsCount)
 
IF @SortBy = '' OR @SortBy = NULL
 SET @SortBy = 'ProductName'
 
IF @SortType = '' OR @SortType = NULL
 SET @SortType = 'Asc'
 
-- Getting the Total Record Count
SELECT @TotalRecordCount = COUNT(*) FROM ProductView
 
-- Getting the records for a particular page
SET @SQL = 
  'SELECT * FROM ' +
  '( ' +
  ' SELECT ' +
  '  ROW_NUMBER() OVER (ORDER BY ' + @SortBy + ' ' +  @SortType + ') AS RowNumber, *' +
  ' FROM ProductView ' +
  ') AS RECORDS ' +
  'Where RowNumber between ' + CAST(@RecordsFrom AS VARCHAR) + ' and ' + CAST(@RecordsTo AS VARCHAR)
 
EXECUTE(@SQL)

Implementation with Sorting in GridView

The source code for this implementation is same except the below code

Add below Style Sheet
th a.sortdown
{
 background: url(../../images/up.png) left center no-repeat;
 padding-left: 22px;
}
th a.sortup
{
 background: url(../../images/down.png) left center no-repeat;
 padding-left: 22px;
}
The ASPX Script (ProcessMessageSorting.aspx)

Add one hidden control hiddenSortExpression in the Page and add sorting event for the GridView.
<div id="GridViewContainer" style="width:100%;height:400px;" onscroll="RefreshRows(this)" >
    <asp:GridView ID="grdViewProducts" CssClass="ViewProducts" runat="server" 
        AutoGenerateColumns="False" TabIndex="1" 
        AllowPaging="false" DataKeyNames="ProductID" Width="100%" GridLines="Both" CellPadding="3" 
        AllowSorting="True" BackColor="White" onsorting="grdViewProducts_Sorting">
        <Columns>
            Add all the columns
        </Columns>
    </asp:GridView>
    <div id="divChildGrid">
    </div>
</div>
<input id="hiddenInitialNoOfPageLoad" type="hidden" value="0" runat="server" />
<input id="hiddenTotalRecords" type="hidden" value="0" runat="server" />
<input id="hiddenPageRecordCount" type="hidden" value="0" runat="server" />
<input id="hiddenSortExpression" type="hidden" value="0" runat="server" />
The C# Codebehind (ProcessMessageSorting.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        hiddenInitialNoOfPageLoad.Value = "2";
        hiddenPageRecordCount.Value = ConfigurationManager.AppSettings["PageRecordCount"].ToString();
        hiddenSortExpression.Value = "ProductName,Ascending";
        BindGrid();
    }
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;

    // Get the SortByExpression and SortType from View State (by default, that will be updated in script)
    string strSortExpression = hiddenSortExpression.Value.Split(",".ToCharArray())[0];
    SortType sortType = (SortType)Enum.Parse(typeof(SortType), hiddenSortExpression.Value.Split(",".ToCharArray())[1], true);

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(hiddenPageRecordCount.Value) * 2; // I am fetching 3 pages at first time

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(1, intPageRecordCount, strSortExpression, sortType, out totalRecordCount); // Getting First three pages
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdViewProducts.DataSource = ProductViewList;
    grdViewProducts.DataBind();

    // Assign the Total Record count to 
    hiddenTotalRecords.Value = totalRecordCount.ToString();

    // Try to find the sorted column
    for (int intRowIndex = 0; intRowIndex < grdViewProducts.Columns.Count; intRowIndex++)
    {
        if (strSortExpression == grdViewProducts.Columns[intRowIndex].SortExpression)
            ((LinkButton)grdViewProducts.HeaderRow.Cells[intRowIndex].Controls[0])
            .CssClass = (sortType == SortType.Ascending ? "sortup" : "sortdown");
    }
}
protected void grdViewProducts_Sorting(object sender, GridViewSortEventArgs e)
{
    string strSortExpression = hiddenSortExpression.Value.Split(",".ToCharArray())[0];
    SortType sortType = (SortType)Enum.Parse(typeof(SortType), hiddenSortExpression.Value.Split(",".ToCharArray())[1], true);

    if (strSortExpression == e.SortExpression)
        sortType = sortType == SortType.Ascending ? SortType.Descending : SortType.Ascending;
    else
    {
        strSortExpression = e.SortExpression;
        sortType = SortType.Ascending;
    }
    hiddenSortExpression.Value = e.SortExpression + "," + sortType.ToString();

    BindGrid();
}
The Javascript
function GetChildGrid(pageId) {

    // Get the Div
    var childGridDiv = document.getElementById("divChildGrid");

    if (childGridDiv) {
        // If the Child Grid is not fetched, then go and fetch it.
        var varTotalRecords = document.getElementById('<%= hiddenTotalRecords.ClientID %>').value;
        var varPageRecordCount = document.getElementById('<%= hiddenPageRecordCount.ClientID %>').value;
        var varSortExpression = document.getElementById('<%= hiddenSortExpression.ClientID %>').value;

        if ((pageId > 0) && (parseInt(varTotalRecords) > (parseInt(varPageRecordCount) * (parseInt(pageId) - 1)))) {

            var url = 'RowsBuilder.aspx?PageId=' + pageId +
                        '&PageRecordsCount=' + varPageRecordCount +
                        '&SortExpression=' + varSortExpression;
            xmlHttp = createAjaxObject();
            if (xmlHttp) {
                xmlHttp.open('get', url, true);
                xmlHttp.onreadystatechange = function () {
                    HandleResponse(pageId);
                }
                varPageOnBehind = varStartAJAXPage;
                xmlHttp.send(null);
            }
        }
    }
}
The output of this code would be:

The vedio output

download the working copy of the source code in C# here and in VB here.

The next post talks about the third implementation.

Tuesday, 15 May 2012

Rendering GridView Rows when scrolling down using AJAX concept - Part 3

In previous post, We had seen how to render the GridView rows from the server and show while scrolling the GridView.

This is a continuation of that post which discuss the third implementation. I had explained the background of this implementation in the first post. So, It is better to read the post for understanding how it is been implemented (but there is no dependency in the code).

Third Implementation
In the previous implementation, the new records are fetched from the server and add at the end of the GridView while scrolling down at the end of the GridView. In this implementation I wish to show how to fetch records page by page in background once the page gets loaded and add at the end of the GridView.

Here, the page will not wait for the user to scroll down to the end of the GridView to trigger the server to fetch the next records, instead the page will show minimum two pages initially while loading the page. Once the page loaded, the page will automatically starts the process to fetch other records page by page from the server and adds to the GridView. Note: I set two pages when page loads - 10 for each page => So 20 records totally, which can be modified programatically.

The use case will be -
  1. Show list of records in a GridView with all sort of customizations possible (such as colors, data formatting etc.,).
  2. The GridView should show all the records in a single page without having pagination.
  3. The GridView should have a scroll bar to scroll and see the records up and down.
  4. Initially the GridView should show only first two pages on the screen (or decided by the programmer on the code).
  5. Once the page loaded, the next 10 records (set programatically) will be fetched from the server and added to the GridView without the need of scrolling down to the end. The same will be repeated in background without user interaction till the end record reached.
I have given two example for this implementation,
  1. Without sorting option on the GridView
  2. With Sorting on the GridView.

Implementation without Sorting in GridView

The ASPX script (FetchOnBackgroundSorting.aspx)
<div id="GridViewContainer" style="width:100%;height:400px;" onscroll="RefreshRows(this)" >
    <asp:GridView ID="grdViewProducts" CssClass="ViewProducts" runat="server" AutoGenerateColumns="False" TabIndex="1" 
        AllowPaging="false" DataKeyNames="ProductID" Width="100%" GridLines="Both" CellPadding="3" 
        AllowSorting="false" BackColor="White">
        <Columns>
            <asp:BoundField DataField="ProductName" HeaderText="Product Name" 
                SortExpression="ProductName">
            <ItemStyle Width="26%" />
            </asp:BoundField>
            <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                SortExpression="CategoryName" >
            <ItemStyle Width="17%" />
            </asp:BoundField>
            <asp:BoundField DataField="QuantityPerUnit" HeaderText="Quantity/Unit" 
                SortExpression="QuantityPerUnit" >
            <ItemStyle Width="17%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" 
                SortExpression="UnitPrice" DataFormatString="{0:#0.00}" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" 
                SortExpression="UnitsInStock" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" 
                SortExpression="UnitsOnOrder" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
            <asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level" 
                SortExpression="ReorderLevel" >
            <ItemStyle Width="10%" />
            </asp:BoundField>
        </Columns>
        <EmptyDataTemplate>
            <div>
                <p style="color:Red">No search results found.</p>
            </div>
        </EmptyDataTemplate>
        <PagerSettings Position="Bottom" />
        <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
        <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
        <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
        <SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
        <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
        <AlternatingRowStyle BackColor="#F7F7F7" />
        <SortedAscendingCellStyle BackColor="#F4F4FD" />
        <SortedAscendingHeaderStyle BackColor="#5A4C9D" />
        <SortedDescendingCellStyle BackColor="#D8D8F0" />
        <SortedDescendingHeaderStyle BackColor="#3E3277" />
    </asp:GridView>
    <div id="divChildGrid">
    </div>
</div>
<input id="hiddenInitialNoOfPageLoad" type="hidden" value="0" runat="server" />
<input id="hiddenTotalRecords" type="hidden" value="0" runat="server" />
<input id="hiddenPageRecordCount" type="hidden" value="0" runat="server" />
The C# Code behind (FetchOnBackgroundSorting.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        hiddenInitialNoOfPageLoad.Value = "2";
        hiddenPageRecordCount.Value = ConfigurationManager.AppSettings["PageRecordCount"].ToString();
        BindGrid();
    }
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(hiddenPageRecordCount.Value) * 2; // I am fetching 3 pages at first time

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(1, intPageRecordCount, string.Empty, SortType.Ascending, out totalRecordCount); // Getting First three pages
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdViewProducts.DataSource = ProductViewList;
    grdViewProducts.DataBind();

    // Assign the Total Record count to 
    hiddenTotalRecords.Value = totalRecordCount.ToString();
}
The Javascript (FetchOnBackgroundSorting.aspx)
var is_ie = (navigator.userAgent.indexOf('MSIE') >= 0) ? 1 : 0;
var is_ie5 = (navigator.appVersion.indexOf("MSIE 5.5") != -1) ? 1 : 0;
var xmlHttp;
var varStartAJAXPage = 3;
var varPageOnBehind = 0;
var varBackgroundJobCanStart = 0;

function pageLoad() {
    GetChildGrid(varStartAJAXPage);
    varBackgroundJobCanStart = 1;
}
window.onload = pageLoad;

function ProcessOnBackground() {
    var time = new Date();
    if (parseInt(varBackgroundJobCanStart) == 1)
        RefreshRows(document.getElementById('divChildGrid'));
}
ProcessOnBackground();
window.setInterval(ProcessOnBackground, 1000);

/* This function requests the HTTPRequest, will be used to render the Dynamic content html markup 
* and it will call HandleResponse to handle the response
*/
function GetChildGrid(pageId) {

    // Get the Div
    var childGridDiv = document.getElementById("divChildGrid");

    if (childGridDiv) {
        // If the Child Grid is not fetched, then go and fetch it.
        var varTotalRecords = document.getElementById('').value;
        var varPageRecordCount = document.getElementById('').value;

        if ((pageId > 0) && (parseInt(varTotalRecords) > (parseInt(varPageRecordCount) * (parseInt(pageId) - 1)))) {

            var url = 'RowsBuilder.aspx?PageId=' + pageId +
                        '&PageRecordsCount=' + varPageRecordCount;
            xmlHttp = createAjaxObject();
            if (xmlHttp) {
                xmlHttp.open('get', url, true);
                xmlHttp.onreadystatechange = function () {
                    HandleResponse(pageId);
                }
                varPageOnBehind = varStartAJAXPage;
                xmlHttp.send(null);
            }
        }
    }
}
function RefreshRows(div) {
    if (varStartAJAXPage - varPageOnBehind > 0) {
        if (div.scrollHeight - (div.scrollTop + div.offsetHeight) < 200) {
            varPageOnBehind = varStartAJAXPage;
            GetChildGrid(varStartAJAXPage);
        }
    }
}
/* This function is used to handler the http response */
function HandleResponse() {

    var childGridDiv = document.getElementById("divChildGrid");

    // If Response completed
    if (xmlHttp.readyState == 4) {

        if (xmlHttp.status == 200) {

            // Here is the response
            var str = xmlHttp.responseText;

            var divRows = document.createElement('div');
            divRows.innerHTML = str;

            childGridDiv.appendChild(divRows);
            varStartAJAXPage++;

            xmlHttp.abort();
        }
    }
}
/* function to create Ajax object */
function createAjaxObject() {
    var ro;
    var browser = navigator.appName;
    if (browser == "Microsoft Internet Explorer") {
        if (xmlHttp != null) {
            xmlHttp.abort();
        }
        ro = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else {
        if (xmlHttp != null) {
            xmlHttp.abort();
        }
        ro = new XMLHttpRequest();
    }
    return ro;
}

/* Get the XML Http Object */
function GetXmlHttpObject(handler) {
    var objXmlHttp = null;
    if (is_ie) {
        var strObjName = (is_ie5) ? 'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP';

        try {
            objXmlHttp = new ActiveXObject(strObjName);
            objXmlHttp.onreadystatechange = handler;
        }
        catch (e) {
            alert('Object could not be created');
            return;
        }
    }
    return objXmlHttp;
}

function xmlHttp_Get(xmlhttp, url) {
    xmlhttp.open('GET', url, true);
    xmlhttp.send(null);
}
The CSS Stylesheet (FetchOnBackgroundSorting.aspx)
#GridViewContainer
{           
    overflow: auto;
}
.ViewProducts
{
    border-top-style:solid;
    border-left-style:solid;
    border-right-style:solid;
    border-bottom-style:none;
}
.ViewChildProducts
{
    border-top-style:solid;
    border-left-style:solid;
    border-right-style:solid;
    border-bottom-style:none;
}
The Respondent ASPX script (RowsBuilder.aspx)
<asp:GridView ID="grdAJAXViewProducts" runat="server" ShowHeader="false" ShowFooter="false"
    AutoGenerateColumns="False" TabIndex="1" AllowPaging="false" DataKeyNames="ProductID"
    Width="100%" GridLines="Both" CellPadding="3" AllowSorting="True"
    BackColor="White" CssClass="ViewProducts">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product Name" 
            SortExpression="ProductName" >
        <ItemStyle Width="26%" />
        </asp:BoundField>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" >
        <ItemStyle Width="17%" />
        </asp:BoundField>
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Quantity/Unit" 
            SortExpression="QuantityPerUnit" >
        <ItemStyle Width="17%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitPrice" HeaderText="Unit Price" 
            SortExpression="UnitPrice" DataFormatString="{0:#0.00}" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock" 
            SortExpression="UnitsInStock" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order" 
            SortExpression="UnitsOnOrder" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
        <asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level" 
            SortExpression="ReorderLevel" >
        <ItemStyle Width="10%" />
        </asp:BoundField>
    </Columns>
    <PagerSettings Position="Bottom" />
    <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
    <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
    <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
    <SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
    <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
    <AlternatingRowStyle BackColor="#F7F7F7" />
    <SortedAscendingCellStyle BackColor="#F4F4FD" />
    <SortedAscendingHeaderStyle BackColor="#5A4C9D" />
    <SortedDescendingCellStyle BackColor="#D8D8F0" />
    <SortedDescendingHeaderStyle BackColor="#3E3277" />
</asp:GridView>
The Code behind file of Respondent (RowsBuilder.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if ((!Page.IsPostBack) && (Request.QueryString["PageId"] != null))
    {
        Response.Clear();
        Response.ContentType = "text/xml";

        BindGrid();

        System.IO.StringWriter stringWrite = new System.IO.StringWriter();
        System.Web.UI.HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWrite);
        grdAJAXViewProducts.RenderControl(htmlWrite);
        Response.Write(stringWrite.ToString());

        Response.End();
    }
}
public override void VerifyRenderingInServerForm(Control control)
{
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;
            
    string strSortExpression = string.Empty;
    SortType sortType = SortType.Ascending;

    if (Request.QueryString["SortExpression"] != null)
    {
        // Get the SortByExpression and SortType from Query String (by default, that will be updated in script)
        strSortExpression = Request.QueryString["SortExpression"].ToString().Split(",".ToCharArray())[0];
        sortType = (SortType)Enum.Parse(typeof(SortType), Request.QueryString["SortExpression"].ToString().Split(",".ToCharArray())[1], true);
    }

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(Request.QueryString["PageRecordsCount"].ToString());

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(Convert.ToInt32(Request.QueryString["PageId"].ToString()), intPageRecordCount, strSortExpression, sortType, out totalRecordCount);
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdAJAXViewProducts.DataSource = ProductViewList;
    grdAJAXViewProducts.DataBind();
}
The Class for fetching the Data from Database (ProductDAO.cs)
public class ProductDAO
{
    public IList<ProductView> GetProducts(int currentPageNo, int pageRecordsCount, string sortBy, SortType sortType, out int totalRecordCount)
    {
        SqlConnection connection = null;
        SqlDataReader dataReader = null;

        try
        {
            IList<ProductView> productViewList = new List<ProductView>();
            connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString());

            SqlDataAdapter dataAdaptor = new SqlDataAdapter("SP_GET_PRODUCTS", connection);
            dataAdaptor.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@CurrentPageNo", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@CurrentPageNo"].Value = currentPageNo;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@PageRecordsCount", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@PageRecordsCount"].Value = pageRecordsCount;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@SortBy", SqlDbType.VarChar));
            dataAdaptor.SelectCommand.Parameters["@SortBy"].Value = sortBy;

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@SortType", SqlDbType.VarChar));
            dataAdaptor.SelectCommand.Parameters["@SortType"].Value = ((sortType == null) ? string.Empty : (sortType == SortType.Ascending ? "Asc" : "Desc"));

            dataAdaptor.SelectCommand.Parameters.Add(new SqlParameter("@TotalRecordCount", SqlDbType.Int));
            dataAdaptor.SelectCommand.Parameters["@TotalRecordCount"].Direction = ParameterDirection.Output;
            connection.Open();

            DataSet dataSet = new DataSet();
            dataAdaptor.Fill(dataSet);

            foreach (DataRow dataRow in dataSet.Tables[0].Rows)
            {
                ProductView productView = new ProductView();
                productView.ProductID = Convert.ToInt32(dataRow["ProductID"].ToString());
                productView.ProductName = dataRow["ProductName"].ToString();
                productView.CompanyName = dataRow["CompanyName"].ToString();
                productView.CategoryName = dataRow["CategoryName"].ToString();
                productView.QuantityPerUnit = dataRow["QuantityPerUnit"].ToString();
                productView.UnitPrice = Convert.ToDouble(dataRow["UnitPrice"].ToString());
                productView.UnitsInStock = Convert.ToInt32(dataRow["UnitsInStock"].ToString());
                productView.UnitsOnOrder = Convert.ToInt32(dataRow["UnitsOnOrder"].ToString());
                productView.ReorderLevel = Convert.ToInt32(dataRow["ReorderLevel"].ToString());

                productViewList.Add(productView);
            }
            // To make more processing time 
            //for (int i = 0; i < (1000000000/2); i++) { int b = 5 + 4 + 3 + 2 - 3; }
            totalRecordCount = Convert.ToInt32(dataAdaptor.SelectCommand.Parameters["@TotalRecordCount"].Value);
            connection.Close();

            //return
            return productViewList;
        }
        catch (Exception ex)
        {
            if (connection != null) connection.Close();
            if (dataReader != null) dataReader.Close();

            // Log
            throw ex;
        }
    }
}
The ProductView entity class
public class ProductView
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string CompanyName { get; set; }
    public string CategoryName { get; set; }
    public string QuantityPerUnit { get; set; }
    public double UnitPrice { get; set; }
    public int UnitsInStock { get; set; }
    public int UnitsOnOrder { get; set; }
    public int ReorderLevel { get; set; }
}
public enum SortType
{
    Ascending,
    Descending
}
The Database script
Create View [dbo].[ProductView]
As
Select Products.ProductID,
  Products.ProductName,
  Suppliers.CompanyName,
  Categories.CategoryName,
  Products.QuantityPerUnit,
  Products.UnitPrice,
  Products.UnitsInStock,
  Products.UnitsOnOrder,
  Products.ReorderLevel
from Products
Join Suppliers on Suppliers.SupplierID = Products.SupplierID
Join Categories on Categories.CategoryID = Products.CategoryID
Go


CREATE PROCEDURE [dbo].[SP_GET_PRODUCTS]
@CurrentPageNo AS INT,
@PageRecordsCount AS INT,
@SortBy AS NVARCHAR(250),
@SortType AS NVARCHAR(250),
@TotalRecordCount AS INT OUTPUT
AS
 
SET NOCOUNT ON
 
DECLARE @SQL AS NVARCHAR(1000)
DECLARE @RecordsFrom AS INT
DECLARE @RecordsTo AS INT
 
-- RecordsFrom => For Ex: CurrentPage 2, PageRecordCount = 10, then (((2-1) * 10) + 1) => ((1 * 10) + 1) => 11
SET @RecordsFrom = (((@CurrentPageNo - 1) * @PageRecordsCount) + 1) 
 
-- @RecordsTo => For Ex: CurrentPage 2, PageRecordCount = 10, then (2 * 10) => 20
SET @RecordsTo = (@CurrentPageNo * @PageRecordsCount)
 
IF @SortBy = '' OR @SortBy = NULL
 SET @SortBy = 'ProductName'
 
IF @SortType = '' OR @SortType = NULL
 SET @SortType = 'Asc'
 
-- Getting the Total Record Count
SELECT @TotalRecordCount = COUNT(*) FROM ProductView
 
-- Getting the records for a particular page
SET @SQL = 
  'SELECT * FROM ' +
  '( ' +
  ' SELECT ' +
  '  ROW_NUMBER() OVER (ORDER BY ' + @SortBy + ' ' +  @SortType + ') AS RowNumber, *' +
  ' FROM ProductView ' +
  ') AS RECORDS ' +
  'Where RowNumber between ' + CAST(@RecordsFrom AS VARCHAR) + ' and ' + CAST(@RecordsTo AS VARCHAR)
 
EXECUTE(@SQL)
Implementation with Sorting in GridView

The code for this implementation will be same as above, with additionally below code.

Add below Style Sheet
th a.sortdown
{
 background: url(../../images/up.png) left center no-repeat;
 padding-left: 22px;
}
th a.sortup
{
 background: url(../../images/down.png) left center no-repeat;
 padding-left: 22px;
}
The ASPX Script (ProcessMessageSorting.aspx)

Add one hidden control hiddenSortExpression in the Page and add sorting event for the GridView.
<div id="GridViewContainer" style="width:100%;height:400px;" onscroll="RefreshRows(this)" >
    <asp:GridView ID="grdViewProducts" CssClass="ViewProducts" runat="server" 
        AutoGenerateColumns="False" TabIndex="1" 
        AllowPaging="false" DataKeyNames="ProductID" Width="100%" GridLines="Both" CellPadding="3" 
        AllowSorting="True" BackColor="White" onsorting="grdViewProducts_Sorting">
        <Columns>
            Add all the columns
        </Columns>
    </asp:GridView>
    <div id="divChildGrid">
    </div>
</div>
<input id="hiddenInitialNoOfPageLoad" type="hidden" value="0" runat="server" />
<input id="hiddenTotalRecords" type="hidden" value="0" runat="server" />
<input id="hiddenPageRecordCount" type="hidden" value="0" runat="server" />
<input id="hiddenSortExpression" type="hidden" value="0" runat="server" />
The C# Codebehind (ProcessMessageSorting.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        hiddenInitialNoOfPageLoad.Value = "2";
        hiddenPageRecordCount.Value = ConfigurationManager.AppSettings["PageRecordCount"].ToString();
        hiddenSortExpression.Value = "ProductName,Ascending";
        BindGrid();
    }
}
private void BindGrid()
{
    // Defining int variable to get the Total Record Count from Data layer
    int totalRecordCount = 0;

    // Get the SortByExpression and SortType from View State (by default, that will be updated in script)
    string strSortExpression = hiddenSortExpression.Value.Split(",".ToCharArray())[0];
    SortType sortType = (SortType)Enum.Parse(typeof(SortType), hiddenSortExpression.Value.Split(",".ToCharArray())[1], true);

    // Getting how many records required to show in the Grid per page
    int intPageRecordCount = Convert.ToInt32(hiddenPageRecordCount.Value) * 2; // I am fetching 3 pages at first time

    ProductDAO objProductDAO = new ProductDAO();

    IList<ProductView> ProductViewList = objProductDAO.GetProducts(1, intPageRecordCount, strSortExpression, sortType, out totalRecordCount); // Getting First three pages
    //Adding one empty row for just to show the grid
    if (ProductViewList.Count == 0)
        ProductViewList.Add(new ProductView());

    grdViewProducts.DataSource = ProductViewList;
    grdViewProducts.DataBind();

    // Assign the Total Record count to 
    hiddenTotalRecords.Value = totalRecordCount.ToString();

    // Try to find the sorted column
    for (int intRowIndex = 0; intRowIndex < grdViewProducts.Columns.Count; intRowIndex++)
    {
        if (strSortExpression == grdViewProducts.Columns[intRowIndex].SortExpression)
            ((LinkButton)grdViewProducts.HeaderRow.Cells[intRowIndex].Controls[0])
            .CssClass = (sortType == SortType.Ascending ? "sortup" : "sortdown");
    }
}
protected void grdViewProducts_Sorting(object sender, GridViewSortEventArgs e)
{
    string strSortExpression = hiddenSortExpression.Value.Split(",".ToCharArray())[0];
    SortType sortType = (SortType)Enum.Parse(typeof(SortType), hiddenSortExpression.Value.Split(",".ToCharArray())[1], true);

    if (strSortExpression == e.SortExpression)
        sortType = sortType == SortType.Ascending ? SortType.Descending : SortType.Ascending;
    else
    {
        strSortExpression = e.SortExpression;
        sortType = SortType.Ascending;
    }
    hiddenSortExpression.Value = e.SortExpression + "," + sortType.ToString();

    BindGrid();
}
The Javascript
function GetChildGrid(pageId) {

    // Get the Div
    var childGridDiv = document.getElementById("divChildGrid");

    if (childGridDiv) {
        // If the Child Grid is not fetched, then go and fetch it.
        var varTotalRecords = document.getElementById('<%= hiddenTotalRecords.ClientID %>').value;
        var varPageRecordCount = document.getElementById('<%= hiddenPageRecordCount.ClientID %>').value;
        var varSortExpression = document.getElementById('<%= hiddenSortExpression.ClientID %>').value;

        if ((pageId > 0) && (parseInt(varTotalRecords) > (parseInt(varPageRecordCount) * (parseInt(pageId) - 1)))) {

            var url = 'RowsBuilder.aspx?PageId=' + pageId +
                        '&PageRecordsCount=' + varPageRecordCount +
                        '&SortExpression=' + varSortExpression;
            xmlHttp = createAjaxObject();
            if (xmlHttp) {
                xmlHttp.open('get', url, true);
                xmlHttp.onreadystatechange = function () {
                    HandleResponse(pageId);
                }
                varPageOnBehind = varStartAJAXPage;
                xmlHttp.send(null);
            }
        }
    }
}
The output of this code would be:


The vedio output download the working copy of the source code in C# here and in VB here.