Thursday 16 May 2013

Asp.Net Nested Grid View Example And Ajax PopUp & Calendar Extender


Asp.Net Nested Grid View Example And Ajax PopUp & Calendar Extender

Asp.Net Nested Grid View Example And Ajax PopUp & Calendar Extender


Nested Grid View is useful to show parent child relationship table. This blog post contains example to build Nested Grid View with Asp.Net.
The Parent Grid View content can be Edited and its Child Grid View loads on the fly if user clicks on parent grid view row.
I use Northwind Sample Database to produce nested Grid View. Also I use DropDownList, TextBox with Ajax PopUp and TextBox with Ajax Calendar to filter database query.
I already have example about Ajax Popup & Calendar in this Url Making Flexible User Input With Asp.Net Ajax PopUp & Calendar Extender Toolkit Controls .
So this blog contains miscelanous example. This might be useful for web internal application that contains complex user input & data layout.

DROPDOWNLIST, AJAX POPUP & CALENDAR CONTROLS

Asp.Net has DropDownList or Combo Box that easily created using a data source. Ajax PopUp & Calendar Extender creates rich user input.
I want to show Order data based on those three control values.
 <ajaxToolkit:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
 </ajaxToolkit:ToolkitScriptManager>
     
<table>
<tr>
<td>Category:</td><td>Product:</td><td>Order Date</td><td>&nbsp;</td>
</tr>
<tr>
<td><asp:DropDownList ID="category" runat="server" DataSourceID="sqlcategory" 
 DataTextField="categoryname" DataValueField="categoryid" AutoPostBack="true" ></asp:DropDownList></td>
<td><asp:TextBox ID="txproduct" runat="server"></asp:TextBox>
</td>
<td><asp:TextBox ID="txorderdate" runat="server"></asp:TextBox></td>
<td><asp:Button ID="btnfind" runat="server" Text=" Find " /></td>
</tr>
</table>

 <asp:Panel ID="panel_product" runat="server" CssClass="popupControl">
 <asp:UpdatePanel ID="UpdatePanel2" runat="server">
 <ContentTemplate>
  <asp:CheckBoxList id="chkproduct" runat="server"
   DataSourceID="sqlproduct" DataTextField="productname" DataValueField="productid" AutoPostBack="true">
     
  </asp:CheckBoxList>
  <center>
  <asp:LinkButton ID="lnkprod" runat="server">OK</asp:LinkButton>
  </center>
 </ContentTemplate>
 </asp:UpdatePanel>
</asp:Panel>
 <ajaxToolkit:PopupControlExtender ID="popup_product" runat="server" TargetControlID="txproduct" PopupControlID="panel_product" 
 Position="Bottom">
</ajaxToolkit:PopupControlExtender>

<ajaxToolkit:CalendarExtender ID="calext_orderdate" runat="server" TargetControlID="txorderdate" Format="yyyy-MM-dd"  PopupPosition="Left"></ajaxToolkit:CalendarExtender>

<asp:SqlDataSource ID="sqlcategory" runat="server"
     ConnectionString="<%$ connectionStrings:northwind_connstr %>"
     SelectCommand="select categoryid, categoryname from categories"></asp:SqlDataSource>

 <asp:SqlDataSource ID="sqlproduct" runat="server"
  ConnectionString="<%$ connectionStrings:northwind_connstr %>"
   SelectCommand="select productid, productname from products where categoryid=@categoryid">
  <SelectParameters>
 <asp:ControlParameter ControlID="category" Type="Int32" Name="categoryid" PropertyName="SelectedValue" />
  </SelectParameters>
  </asp:SqlDataSource>
ToolkitScriptManager must be included in order to use Ajax PopUp & Calendar extender.
‘category’ DropDownList data is populated using ‘sqlcategory’ sql data source to fill in with list of product categories.
The actual PopUp Control is a panel that contains UpdatePanel, ContentTemplate tags. We can write any Asp.net control Inside ContentTemplate. Here I have checkboxlist and linkbutton to confirm user choice.
PopupControlExtender is a controller to decide which one is a Popup Control, its Target and position relative to target by setting PopupControlID, TargetControlID, and Position relatively.
Likewise with CalendarExtender, it has TargetControlID, PopupPosition and also Date Format.
CheckBoxList inside panel Popup Control populated with ‘sqlproduct’. This data source retrieve list of products based on categoryid that chosen in ‘category’ DropDownList.
I have ‘Find’ button to find specific Order from Northwind Database based on above user inputs.

NESTED GRID VIEW

After user clicks ‘Find’ button then query is running and resulting a Grid View.
Source of GridView:
<asp:GridView ID="gdvResult" runat="server" AutoGenerateColumns="false" DataKeyNames="orderid" 
         AllowSorting="true">
<Columns>
 <asp:TemplateField>
 <ItemTemplate>
    <a href="javascript:collapseExpand('oid-<%# Eval("orderid") %>');">+</a>
        
   <input type='hidden' id='hid_orderid' name='hidorderid' Value='<%# Eval("orderid") %>' />
  </ItemTemplate>
 </asp:TemplateField> 
 <asp:TemplateField HeaderText="Order id" SortExpression="orderid">
  <ItemTemplate>
    
   <%# Eval("orderid")%>
   <asp:HiddenField ID="orderid" runat="server" Value='<%# Eval("orderid") %>' />
   
  </ItemTemplate>
 </asp:TemplateField>
 
 <asp:TemplateField HeaderText="Order Date" SortExpression="orderdate" >
  <ItemTemplate>
   <%# Eval("orderdate")%>
   
  </ItemTemplate>
  <EditItemTemplate>
  <div style="width:100px"><asp:TextBox ID="orderdate" runat="server" text='<%#Bind("orderdate") %>' ClientIDMode="Inherit" style="width:60px"></asp:TextBox> 
   <asp:ImageButton ID="imgbtnorddt" runat="server" ImageUrl="~/Calendar_scheduleHS.png" />
  </div>                    
  </EditItemTemplate>

 </asp:TemplateField>
 
 <asp:TemplateField HeaderText="Required Date" SortExpression="requireddate">
 <ItemTemplate>
   <%# Eval("requireddate")%>
   
  </ItemTemplate>
  <EditItemTemplate>                    
   <div style="width:100px"><asp:TextBox ID="requireddate" runat="server" text='<%#Bind("requireddate") %>' ClientIDMode="Inherit" style="width:60px"></asp:TextBox> 
   <asp:ImageButton ID="imgbtnreqdt" runat="server" ImageUrl="~/Calendar_scheduleHS.png" />
  </div> 
  </EditItemTemplate>
 </asp:TemplateField>
  
 <asp:TemplateField HeaderText="Freight" SortExpression="freight">
  <ItemTemplate>
   <%# Eval("freight")%>
   
  </ItemTemplate>
  <EditItemTemplate>
   <asp:TextBox ID="freight" runat="server" text='<%#Bind("freight") %>'></asp:TextBox>
  </EditItemTemplate>
 </asp:TemplateField>
 
 <asp:TemplateField HeaderText="Ship Name" SortExpression="shipname">
  <ItemTemplate>
   <%# Eval("shipname")%>
   
  </ItemTemplate>
  <EditItemTemplate>
   <asp:TextBox ID="shipname" runat="server" text='<%#Bind("shipname") %>'></asp:TextBox>
  </EditItemTemplate>
 </asp:TemplateField>
   
 <asp:TemplateField>
  <ItemTemplate>
   <asp:Button ID="editbutton" Text="Edit" runat="server" CommandName="Edit" />
  </ItemTemplate>
  <EditItemTemplate>
   <asp:Button ID="updatebutton" Text="Update" runat="server" CommandName="Update" />
   <asp:Button ID="cancelbutton" Text="Cancel" runat="server" CommandName="Cancel" />
  </EditItemTemplate>

 </asp:TemplateField>
 <asp:TemplateField>
  <ItemTemplate>
   <tr><td colspan="100%">
   <div id='oid-<%# Eval("orderid") %>' style="display:none;position:relative; left:25px">
   <asp:GridView ID="gdvnested" runat="server" AutoGenerateColumns="false" >
   <Columns>
    <asp:TemplateField HeaderText="Product id">
     <ItemTemplate>
      <%# Eval("ProductID") %>
      
     </ItemTemplate>
    </asp:TemplateField>

    <asp:TemplateField HeaderText="Product Name">
     <ItemTemplate>
      <%#Eval("ProductName") %>
     </ItemTemplate>
    </asp:TemplateField>

    <asp:TemplateField HeaderText="UnitPrice">
     <ItemTemplate>
      <%# Eval("UnitPrice")%>
     </ItemTemplate>
     
    </asp:TemplateField>

     <asp:TemplateField HeaderText="Quantity">
     <ItemTemplate>
      <%# Eval("Quantity")%>
     </ItemTemplate>
       
    </asp:TemplateField>

     <asp:TemplateField HeaderText="Discount">
     <ItemTemplate>
      <%# Eval("Discount")%>
     </ItemTemplate>
     
    </asp:TemplateField>
   </Columns>
   </asp:GridView>
   </div>
   </td></tr>
   
  </ItemTemplate>
 </asp:TemplateField>
</Columns>
<EmptyDataTemplate>
 No Records Found
</EmptyDataTemplate>
</asp:GridView>

<asp:SqlDataSource ID="sqlresult" runat="server"
     ConnectionString="<%$ connectionStrings:northwind_connstr %>"></asp:SqlDataSource>

<asp:SqlDataSource ID="sqlnested" runat="server" ConnectionString="<%$ connectionStrings:northwind_connstr %>"></asp:SqlDataSource>
In last column section I have templatefield contains child grid view. This child grid view contained in tables row (<TR />) and a div element to implement left padding.
‘sqlresult’ and ‘sqlnested’ sqldatasources SelectCommand is created programmatically due to I want to display the child grid view on demand. It is better at performance and web server do not load heavily.
In first column section I have this <a href="javascript:collapseExpand('oid-');">+ to show/hide child grid view using javascript.
Since showing and hiding a child grid view is a client process then javascript must be used.
Full Js source:
<script type="text/javascript">
 function trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
 }
 function collapseExpand(obj) {
  var gvObject = document.getElementById(obj);
  if (gvObject.style.display == "none") {

   if (trim(gvObject.innerHTML) == "<DIV></DIV>") {
    __doPostBack("hid_orderid", "nested_gv");
   } else {
    gvObject.style.display = "inline";
   }
  
  }
  else {
   gvObject.style.display = "none";
  }
 }

 function expandonload(obj) {
  var gvObject = document.getElementById(obj);
  gvObject.style.display = "inline";
 }

 function open_calendar(openerid, selecteddate) {
  window.open("DatePicker.aspx?openerid=" + openerid + "&selecteddate=" + selecteddate,"","width=220,height=210");
  return false;
 }
</script>
In collapseExpand(..) function I check whether a respective child gridview is already loaded or not. If it is not loaded then create it by submiting a spesific postback using __doPostBack("hid_orderid", "nested_gv"); function.
‘hid_orderid’ is Event Target Control ID that contains field ‘OrderID’ value and ‘nested_gv’ is a Event Argument. These value is used in code behind file.
Otherwise, if child grid view is loaded then show it with css style display value ‘inline’ -> gvObject.style.display = "inline".
At every date field like orderdate, requireddate Imagebutton applied to show calendar popup. This is also accomplished in code behind file.
Full code behind file source code:
Partial Class SearchNGridView
    Inherits System.Web.UI.Page
    Dim nestedOrderID As String
    
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Page.IsPostBack Then
            If Request.Form("__EVENTARGUMENT") = "nested_gv" Then
                nestedOrderID = Request.Form("hidorderid")
    
                gdvResultBind()
                ScriptManager.RegisterStartupScript(Me, Me.GetType(), "expand_onload", "expandonload('oid-" & nestedOrderID & "');", True)
            End If
        End If

    End Sub

    
    Protected Sub lnkprod_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles lnkprod.Click
        Dim strprod As New StringBuilder()
        Dim selected_no As Integer = 0
        For i As Integer = 0 To chkproduct.Items.Count - 1
            If chkproduct.Items(i).Selected Then
                If selected_no = 0 Then
                    strprod.Append(chkproduct.Items(i).Value)
                Else
                    strprod.Append("," & chkproduct.Items(i).Value)
                End If
                selected_no = selected_no + 1
            End If
        Next
        popup_product.Commit(strprod.ToString())
    End Sub

   
    Protected Sub btnfind_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnfind.Click
        Session("sqlsearch") = "select distinct orders.orderid, Convert(varchar(10),orders.orderdate,101) as orderdate, convert(varchar(10),requireddate,101) as requireddate, freight, shipname from orders, [order details] od, products " & _
                " where orders.orderid=od.orderid and od.productid=products.productid and od.productid in (" & txproduct.Text & ") and orders.orderdate='" & txorderdate.Text & "'"
        gdvResultBind()
    End Sub

    Protected Sub gdvResult_RowCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gdvResult.RowCreated
        Dim selectedorderdate As String
        Dim selectedrequireddate As String

        Dim orderdate As TextBox = DirectCast(e.Row.FindControl("orderdate"), TextBox)
        Dim requireddate As TextBox = DirectCast(e.Row.FindControl("requireddate"), TextBox)
        Dim btnorddt As ImageButton = DirectCast(e.Row.FindControl("imgbtnorddt"), ImageButton)
        Dim btnreqdt As ImageButton = DirectCast(e.Row.FindControl("imgbtnreqdt"), ImageButton)

        If DataBinder.Eval(e.Row.DataItem, "orderdate") IsNot Nothing Then
            selectedorderdate = DataBinder.Eval(e.Row.DataItem, "orderdate").ToString()
        End If

        If DataBinder.Eval(e.Row.DataItem, "orderdate") IsNot Nothing Then selectedrequireddate = DataBinder.Eval(e.Row.DataItem, "requireddate").ToString()

        If btnorddt IsNot Nothing Then
            btnorddt.Attributes.Add("onclick", "javascript:return open_calendar('gdvResult_" & orderdate.ClientID & "','" & selectedorderdate & "')")
        End If

        If btnreqdt IsNot Nothing Then
            btnreqdt.Attributes.Add("onclick", "javascript:return open_calendar('gdvResult_" & requireddate.ClientID & "','" & selectedrequireddate & "')")
        End If
    End Sub

    Protected Sub gdvResult_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gdvResult.RowDataBound

        If e.Row.RowType = DataControlRowType.DataRow Then
            Dim orderid As String = Convert.ToString(DataBinder.Eval(e.Row.DataItem, "orderid"))

            If orderid = nestedOrderID And nestedOrderID <> "" Then

                Dim gvchild As GridView = DirectCast(e.Row.FindControl("gdvnested"), GridView)
                Dim sql As String = "select od.orderid, od.productid, productname, od.unitprice, od.quantity, od.discount from [order details] od inner join products on od.productid=products.productid where orderid=" & orderid
                sqlnested.SelectCommand = sql
                gvchild.DataSourceID = "sqlnested"
                gvchild.DataBind()
            End If
                        
        End If
    End Sub

    Protected Sub gdvResult_Sorting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewSortEventArgs) Handles gdvResult.Sorting

        Dim sortDirection As String
        If e.SortDirection = WebControls.SortDirection.Ascending Then
            sortDirection = "DESC"
        Else
            sortDirection = "ASC"
        End If
        Session("sqlsearch") = Session("sqlsearch") & " order by " & e.SortExpression & " " & sortDirection
        gdvResultBind()
    End Sub

    Protected Sub gdvResult_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles gdvResult.RowCommand
        If e.CommandName = "Edit" Then

        ElseIf e.CommandName = "Update" Then

            Dim gvr As GridViewRow = CType(CType(e.CommandSource, Button).NamingContainer, GridViewRow)

            Dim orderid As HiddenField = CType(gvr.FindControl("orderid"), HiddenField)
            Dim freight As TextBox = CType(gvr.FindControl("freight"), TextBox)
            Dim orderdate As TextBox = CType(gvr.FindControl("orderdate"), TextBox)
            Dim requireddate As TextBox = CType(gvr.FindControl("requireddate"), TextBox)
            Dim shipname As TextBox = CType(gvr.FindControl("shipname"), TextBox)

            Dim sqlupdate = "update orders set orderdate='" & orderdate.Text & "', " & _
                    " requireddate='" & requireddate.Text & "', freight=" & freight.Text & ", shipname='" & shipname.Text & "' where orderid=" & orderid.Value

            sqlresult.UpdateCommand = sqlupdate
            sqlresult.Update()
        End If
        gdvResultBind()
    End Sub
    Private Sub gdvResultBind()
        sqlresult.SelectCommand = Session("sqlsearch")
        gdvResult.DataSourceID = "sqlresult"
        gdvResult.DataBind()
    End Sub

End Class
Several things I want to point are
  1. Parent Gridview data is populated in btnfind_Click(..)
  2. In Page_Load(..) procedure, I catch __EVENTARGUMENT element with value ‘nested_gv’ to load child grid view on demand. The creation itself is during Binding the parent gridview at gdvResult_RowDataBound(..)
    After child grid view is created then show it usingScriptManager.RegisterStartupScript(Me, Me.GetType(), "expand_onload", "expandonload('oid-" & nestedOrderID & "');", True). This code makes expanonload Js function running on load event.
  3. In gdvResult_RowDataBound(..) procedure, I check whether parent order id is same with child. if it is same then SelectCommand in sqlnested sqldatasource is written and then child grid view populates.
  4. In gdvResult_RowCreated(..) procedure, I add ‘onclick’ attribute to ImageButton to show calendar or datapicker as popup. Basically, it opens an Url using javascript open_calendar function. Tihs Url contains Calendar control only.
    DatePicker.aspx
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
        <script type="text/javascript" language="javascript">
            function setdate(selecteddate, openerid) {
                window.opener.document.getElementById(openerid).value = selecteddate;
                window.close();
            }
        </script>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:Calendar ID="Calendar1" runat="server" BackColor="White" 
                BorderColor="#999999" CellPadding="4" DayNameFormat="Shortest" 
                Font-Names="Verdana" Font-Size="8pt" ForeColor="Black" Height="180px" 
                Width="200px">
                <DayHeaderStyle BackColor="#CCCCCC" Font-Bold="True" Font-Size="7pt" />
                <NextPrevStyle VerticalAlign="Bottom" />
                <OtherMonthDayStyle ForeColor="#808080" />
                <SelectedDayStyle BackColor="#666666" Font-Bold="True" ForeColor="White" />
                <SelectorStyle BackColor="#CCCCCC" />
                <TitleStyle BackColor="#999999" BorderColor="Black" Font-Bold="True" />
                <TodayDayStyle BackColor="#CCCCCC" ForeColor="Black" />
                <WeekendDayStyle BackColor="#FFFFCC" />
            </asp:Calendar>
        </div>
        </form>
    </body>
    </html>
    
    
    DatePicker.aspx.vb
     Partial Class DatePicker
        Inherits System.Web.UI.Page
    
        Dim openerid, selecteddate As String
    
        Protected Sub Calendar1_SelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Calendar1.SelectionChanged
            ScriptManager.RegisterStartupScript(Me, Me.GetType(), "set_date", "setdate('" & Calendar1.SelectedDate & "','" & openerid & "');", True)
        End Sub
    
        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            openerid = Request.QueryString("openerid")
            selecteddate = Request.QueryString("selecteddate")
    
            Calendar1.SelectedDate = Date.Parse(selecteddate)
            Calendar1.VisibleDate = Calendar1.SelectedDate
        End Sub
    End Class
    
    The open_calendar(..) Js function contains two querystring which are opener element id and current respective date value. So at Page_load, I retrieved them to set initial date on Calendate control.
    When user change a date then opener element value set to chosen date and the DatePicker.aspx page will be closed.
Screen shots:


1 comment: