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.

15 Responses to “Hierarchical GridView in ASP.NET with AJAX, JQuery implementation - Part 1”

  • Anonymous says:
    22 August 2012 at 16:21

    On executing I am getting a Message "error" occuered. Do I need to put the dll file. Please help me I am new in ajax and jquery

  • Thiru says:
    22 August 2012 at 17:23

    Hi. Verify your Child Grid page exist and mentioned as per the file name in the JQuery in line#26.

    The error message shown as we have coded to alert if any error. If child page exist as defined in the JQuery, check the child page load event by having breakpoint. If the execution comes there, there may be an error in the child page and returning run time error.

    If the child page has not runtime error, there may be error on defining the parameter.

    I would suggest you to run the code provided in the page and verify if this code runs perfectly.
    If yes, there may be issue on your code. So required to check.

  • Anonymous says:
    18 September 2012 at 01:52

    Hi. This seems to run under ie, but didn't have much luck with Firefox. The loading icon appears, but doesn't return to show the order details. Do you know what the issue might be?

  • Anonymous says:
    25 February 2013 at 22:08

    Same problem. Does not work with firefox and Chrome throws an exception cause of the contenttype of the childpage. if i change it to html or plain chrome has no error, but behaves like firefox. Loading icon appears and nothing else is happening. Only ie does it correctly

  • Thirumalai M says:
    25 February 2013 at 22:14

    Hi, I have corrected that issue in my code. I did not posted that, I will update this post by tomorrow.

  • Anonymous says:
    26 February 2013 at 15:44

    Thanks!

    You forgot 2 closing brackets in the js :>

    after the error and before else you forgot to close the ajax-command with });

    But: Chrome now does it right. But Firefox still does not :/
    I have 2 Rows in the parent grid coming from my DB and sometimes (without changing code) it works for the second row, but never for the first.

    Oh and I use your example code without any changes (except the SQLs of course)

  • Anonymous says:
    26 February 2013 at 16:14

    Got it!
    Now it works fine in firefox, chrome and IE. I mixed up the code of your old and your new version a bit.
    Here are my changes:

    $.ajax({
    url: 'ChildGridBuilder.aspx',
    type: 'POST',
    data: { OrderID: orderId },
    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(xhr.status);
    alert(thrownError);
    }
    });


    and of course i use
    Request.Form["OrderID"].ToString()
    instead of
    Request.QueryString["OrderID"].ToString()


    next step: add a third level :D thanks for this tutorial !

  • Thirumalai M says:
    26 February 2013 at 17:01

    Hi, Thanks for pointing out the js closing brackets mistakes. I replaced a part of that js, so it made that issue. I also uploaded the source code, so you could download the file any verify once.

    Three level :D... I would suggest you to get soome idea from thise example and try on yourself. I will sure help you if you have any issues.

  • GBTagHB says:
    26 February 2013 at 22:14

    Works fine now with 3 levels :>
    Thanks for this nice tutorial! You sir, ended a 1-week odyssey.

  • Thirumalai M says:
    27 February 2013 at 14:24

    Hi GBTagHB. Thanks you....

  • Anonymous says:
    12 June 2013 at 20:41

    Have a problem with this code in Chrome, IE and Opera :/
    When i open a subgrid with the event, the response is manipulated and chrome, ie and opera always give me an invalid view state error ...
    All happens somewhere here:
    Response.Write(stringWrite.ToString());

    Any suggestions?

  • Anonymous says:
    13 June 2013 at 21:33

    nvm ... found a solution... i accidently rendered the form tag, not the gridview of the subgrid ...

  • Halcón Dorado says:
    3 August 2013 at 02:45

    Great solution, but it fails when the Default.aspx is inside of masterpage.

    When changing index page, ChildGridBuilder.aspx does not displaying. Any ideas to resolve it ?
    Thank you a lot.




  • Halcón Dorado says:
    3 August 2013 at 03:51

    I solve it, removing the Scriptmanager in the masterpage.

  • Unknown says:
    10 February 2017 at 05:04

    Great Blog Post....I realize this is old, but I am struggling with getting a 3rd level to work...basically, my click event for the dropdown arrow does not respond. Any suggestions?

Post a Comment