0

I am trying to build a webforms application with a nested gridview. The problem is that nothing happens when I click on the "+" on a row in the parent gridview. See below:

enter image description here

I have the following code.

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Add Bill of Materials</h2>

    <script type="text/javascript">
        $(document).ready(function () {
            window.setTimeout(function () {
                $(".alert").fadeTo(1500, 0).slideUp(500, function () {
                    $(this).remove();
                });
            }, 3000);
        });

        function divexpandcollapse(divname) {
            var img = "img" + divname;
            if ($("#" + img).attr("src") == "../../Images/plus.png") {
                $("#" + img)
                    .closest("tr")
                    .after("<tr><td></td><td colspan = '100%' > " + $("#" + divname)
                        .html() + "</td></tr>")
                $("#" + img).attr("src", "../../Images/minus.png");
            } else {
                $("#" + img).closest("tr").next().remove();
                $("#" + img).attr("src", "../../Images/plus.png");
            }
        }
    </script>

    <div runat="server" visible="false" id="AlertDanger" class="alert alert-danger">
        <a href="#" class="close" data-dismiss="alert">&times;</a>BMDetails
        <strong>You must choose a date</strong>
    </div>

    <div runat="server" visible="false" id="AlertSuccess" class="alert alert-success">
        <a href="#" class="close" data-dismiss="alert">&times;</a>
        <strong>Purchase requisition saved successfully</strong>
    </div>
    <asp:Panel ID="BMDetails" runat="server">
        <div>
            <asp:UpdatePanel ID="UpdatePanelBM" runat="server">
                <ContentTemplate>
                    <fieldset class="form-horizontal">
                        <div class="row">
                            <div class="control-group col-sm-4">
                                <asp:Label runat="server" CssClass="col-sm-6 control-label" Style="text-align: left">Doc No.</asp:Label>
                                <div class="form-group col-sm-6">
                                    <asp:Label ID="lblDocNum" runat="server" CssClass="form-control" BackColor="#E6E6E6"></asp:Label>
                                </div>
                            </div>
                            <div class="control-group col-sm-8">
                                <asp:Label runat="server" CssClass="col-sm-6 control-label">Cust. PO No.</asp:Label>
                                <div class="form-group col-sm-6">
                                    <asp:TextBox ID="txtPurchaseDocNum" runat="server" AutoPostBack="true" CssClass="form-control" OnTextChanged="txtPurchaseDocNum_TextChanged"></asp:TextBox>
                                    <ajaxToolkit:AutoCompleteExtender
                                        ID="AutoCompleteExtender1"
                                        runat="server"
                                        TargetControlID="txtPurchaseDocNum"
                                        ServicePath="../../Web_Service/PurchaseOrders.asmx"
                                        ServiceMethod="GetPurchaseOrders"
                                        MinimumPrefixLength="2"
                                        EnableCaching="true"
                                        CompletionSetCount="10"
                                        CompletionInterval="10"
                                        DelimiterCharacters=";, :"
                                        ShowOnlyCurrentWordInCompletionListItem="true">
                                    </ajaxToolkit:AutoCompleteExtender>
                                    <asp:RequiredFieldValidator runat="server" ControlToValidate="txtPurchaseDocNum" Display="Dynamic"
                                        CssClass="text-danger" ErrorMessage="The Cust. Purchase Order No field is required." />
                                </div>
                            </div>
                        </div>
                    </fieldset>
                    <asp:GridView ID="bMGridView"
                        runat="server"
                        AutoGenerateColumns="False"
                        AllowPaging="True"
                        AllowSorting="True"
                        ShowFooter="True"
                        OnPageIndexChanging="bMParentGrid_PageIndexChanging"
                        OnRowDataBound="bMParentGrid_RowDataBound"
                        OnRowCommand="bMParentGrid_RowCommand"
                        PagerStyle-CssClass="bs-pagination"
                        ShowHeaderWhenEmpty="True"
                        EmptyDataText="Select Customer PO number"
                        CssClass="table table-striped table-bordered table-hover table-condensed">
                        <Columns>                           
                            <asp:TemplateField ItemStyle-Width="20px">
                                <ItemTemplate>
                                    <a href="JavaScript:divexpandcollapse
                                    ('div<%# Eval("Item") %>');">
                                        <img alt="Details" id="imgdiv<%# Eval
                                        ("Item") %>"
                                            src="../../Images/plus.png" />
                                    </a>                                    
                                    <div id="div<%# Eval("Item") %>" style="display: none;">                                       
                                        <asp:GridView ID="bMChildGrid"
                                            runat="server"
                                            AutoGenerateColumns="False"
                                            AllowPaging="True"
                                            AllowSorting="True"
                                            ShowFooter="True"
                                            OnRowEditing="bMChildGrid_RowEditing"
                                            OnRowCancelingEdit="bMChildGrid_RowCancelingEdit"
                                            OnRowUpdating="bMChildGrid_RowUpdating"
                                            PagerStyle-CssClass="bs-pagination"
                                            ShowHeaderWhenEmpty="True"
                                            EmptyDataText="No Records Found"
                                            CssClass="table table-striped table-bordered table-hover table-condensed">
                                            <Columns>
                                                <asp:TemplateField ItemStyle-Width="30px" HeaderText="#">
                                                    <ItemTemplate>
                                                        <asp:Label ID="lblLineNum" Text='<%# Container.DataItemIndex + 1 %>' runat="server" />
                                                    </ItemTemplate>
                                                </asp:TemplateField>
                                                <asp:TemplateField ItemStyle-Width="500px" HeaderText="Item">
                                                    <ItemTemplate>
                                                        <asp:Label ID="lblItem" runat="server"
                                                            Text='<%# Bind("Item")%>'></asp:Label>
                                                    </ItemTemplate>
                                                    <EditItemTemplate>
                                                        <asp:TextBox ID="txtItem" runat="server" Width="500px" Text='<%# Bind("Item")%>'></asp:TextBox>
                                                        <ajaxToolkit:AutoCompleteExtender
                                                            ID="AutoCompleteExtender3"
                                                            runat="server"
                                                            TargetControlID="txtItem"
                                                            ServicePath="../../Web_Service/Items.asmx"
                                                            ServiceMethod="GetItems"
                                                            MinimumPrefixLength="2"
                                                            EnableCaching="true"
                                                            CompletionSetCount="10"
                                                            CompletionInterval="10"
                                                            DelimiterCharacters=";, :"
                                                            ShowOnlyCurrentWordInCompletionListItem="true">
                                                        </ajaxToolkit:AutoCompleteExtender>
                                                        <asp:RequiredFieldValidator runat="server" ControlToValidate="txtItem" Display="Dynamic" ValidationGroup="Edit"
                                                            CssClass="text-danger" ErrorMessage="The Item field is required." />
                                                    </EditItemTemplate>
                                                    <FooterTemplate>
                                                        <asp:TextBox ID="txtItem" runat="server" Width="500px"></asp:TextBox>
                                                        <ajaxToolkit:AutoCompleteExtender
                                                            ID="AutoCompleteExtender4"
                                                            runat="server"
                                                            TargetControlID="txtItem"
                                                            ServicePath="../../Web_Service/Items.asmx"
                                                            ServiceMethod="GetItems"
                                                            MinimumPrefixLength="2"
                                                            EnableCaching="true"
                                                            CompletionSetCount="10"
                                                            CompletionInterval="10"
                                                            DelimiterCharacters=";, :"
                                                            ShowOnlyCurrentWordInCompletionListItem="true">
                                                        </ajaxToolkit:AutoCompleteExtender>
                                                        <asp:RequiredFieldValidator runat="server" ControlToValidate="txtItem" Display="Dynamic" ValidationGroup="Insert"
                                                            CssClass="text-danger" InitialValue="-1" ErrorMessage="The Item field is required." />
                                                    </FooterTemplate>
                                                </asp:TemplateField>
                                                <asp:TemplateField ItemStyle-Width="120px" HeaderText="Required Qty.">
                                                    <ItemTemplate>
                                                        <asp:Label ID="lblQuantity" runat="server"
                                                            Text='<%# Bind("Quantity")%>'></asp:Label>
                                                    </ItemTemplate>
                                                    <EditItemTemplate>
                                                        <asp:TextBox ID="txtQuantity" runat="server" Width="100px"
                                                            Text='<%# Bind("Quantity")%>'></asp:TextBox>
                                                        <asp:RequiredFieldValidator runat="server" ControlToValidate="txtQuantity" Display="Dynamic" ValidationGroup="Edit"
                                                            CssClass="text-danger" ErrorMessage="The Quantity field is required." />
                                                        <asp:RegularExpressionValidator ControlToValidate="txtQuantity" runat="server" CssClass="text-danger" Display="Dynamic"
                                                            ErrorMessage="Only integers allowed." ValidationExpression="^(0|[1-9]\d*)$"
                                                            ValidationGroup="Edit"></asp:RegularExpressionValidator>
                                                    </EditItemTemplate>
                                                    <FooterTemplate>
                                                        <asp:TextBox ID="txtQuantity" runat="server" Width="100px"></asp:TextBox>
                                                        <asp:RequiredFieldValidator runat="server" ControlToValidate="txtQuantity" Display="Dynamic" ValidationGroup="Insert"
                                                            CssClass="text-danger" ErrorMessage="The Quantity field is required." />
                                                        <asp:RegularExpressionValidator ControlToValidate="txtQuantity" runat="server" CssClass="text-danger" Display="Dynamic"
                                                            ErrorMessage="Only integers allowed." ValidationExpression="^(0|[1-9]\d*)$"
                                                            ValidationGroup="Insert"></asp:RegularExpressionValidator>
                                                    </FooterTemplate>
                                                </asp:TemplateField>
                                            </Columns>
                                        </asp:GridView>
                                    </div>
                                </ItemTemplate>
                            </asp:TemplateField>

                            <asp:TemplateField ItemStyle-Width="30px" HeaderText="#">
                                <ItemTemplate>
                                    <asp:Label ID="lblLineNum" Text='<%# Container.DataItemIndex + 1 %>' runat="server" />
                                </ItemTemplate>
                            </asp:TemplateField>

                            <asp:BoundField DataField="Item" HeaderText="Item" />
                            <asp:BoundField DataField="Quantity" HeaderText="Quantity" />

                        </Columns>
                    </asp:GridView>

                    <div class="form-group">
                        <div class="row">
                            <div class="col-sm-3">
                                <asp:Button runat="server" ID="InsertButton" OnClick="Insert" Text="Add" CssClass="btn btn-block btn-primary" />
                            </div>
                            <div class="col-sm-3">
                                <asp:Button runat="server" ID="CancelButton" OnClick="Cancel" Text="Cancel" CausesValidation="false" CssClass="btn btn-block btn-default" />
                            </div>
                        </div>
                    </div>
                </ContentTemplate>
            </asp:UpdatePanel>
        </div>
    </asp:Panel>

</asp:Content>

This is what I have in the code behind:

protected void bMParentGrid_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                string item = Convert.ToString(DataBinder.Eval(e.Row.DataItem, "Item"));

                string selectedItem = item.Substring(0, item.IndexOf(' ')).Trim();

                GridView bMChildGrid = e.Row.FindControl("bMChildGrid") as GridView;

                // Create Data Table
                DataTable dt = new DataTable();
                dt.Columns.Add("lineNum", typeof(int));
                dt.Columns.Add("Item", typeof(string));
                dt.Columns.Add("Quantity", typeof(int));

                dt.Rows.Add(dt.NewRow());
                bMChildGrid.DataSource = dt;
                bMChildGrid.DataBind();               
            }
        }

What am I doing wrong? Clicking on a parent grid row does not reveal the child grid embedded in it.

8
  • Web Forms changes the ID's of controls by default. Did you inspect the DOMC to make sure the ID's in your JavaScript line up with the ID's in the DOM? Commented Jun 14, 2021 at 17:20
  • @mason You mean check in Chrome or Firefox? I get this when I click on the "+" Uncaught Error: Syntax error, unrecognized expression: #imgdivRE002 - Utility Wedge Set 3pc (DG) Commented Jun 14, 2021 at 17:43
  • Yes, I mean check it in the browser. That's where the DOM lives. Did you figure out what line of code throws that error? Is that really the input you expected to pass to it? Commented Jun 14, 2021 at 17:56
  • 1
    Line 193 is not that. You're looking at the ASPX. You need to look at the actual rendered HTML, on the client side. ASPX is not HTML, and to understand Web Forms you need to understand how ASPX gets rendered into HTML. Commented Jun 14, 2021 at 18:27
  • 1
    Ignore the fact this is webforms. This is mosty a jquery/javascript issue. Look at html sent to the browser using "view source" in the browser of your choice. Work with that. Alos try and lose your dependency on ids. Use parent/child or sibling relationships instead. Commented Jun 15, 2021 at 5:08

1 Answer 1

3

Hum, I don't think you should try nesting gridview in gridview.

The problem is that GridView does not support "row span" like a list view does.

When you expand that GridView, it going to try and fit in ONE column.

so the grid might look like this:

enter image description here

So I have the + sign to expand/show a nested gridview, and I will get this:

(expanding in this example shows people booked in hotel)

you get this effect:

enter image description here

Now you might with extra hammers, saws, nails and hammering away at this get the gridview to expand and appear BELOW the row.

But, GridView does not support well (if at all) the ability to have a multi-line row.

However, listview does. So you could well keep your nested (child) gridview, and drop that into a ListView.

So, the listview will (should/can) look quite much the same. Say like this:

enter image description here

But, I can have the nested control (gridview) take up a WHOLE row, so when I expand this in the listview, I get this:

enter image description here

As you can see, this looks much better. And I don't have to fight the system and latyouts - it works a lot better.

So, here is my ListView markup:

    <asp:ListView ID="ListView1" runat="server" DataKeyNames="ID" >

       <ItemTemplate>
          <tr style="">
            <td><asp:Button ID="cmdView" runat="server" Text="+" CommandName="Select" /></td>
            <td><asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
            <td><asp:Label ID="CityLabel" runat="server" Text='<%# Eval("City") %>' /></td>
            <td><asp:Label ID="ProvinceLabel" runat="server" Text='<%# Eval("Province") %>' /></td>
            <td><asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' /></td>
          </tr>

          <tr>
            <td colspan="5">
               <asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" 
                 DataKeyNames="ID" CssClass="table table-hover" style="display:none"  >
                    <Columns>
                        <asp:BoundField DataField="Firstname" HeaderText="Firstname" SortExpression="Firstname" />
                        <asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
                        <asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
                    </Columns>
                </asp:GridView>

            </td>
           </tr>

           </ItemTemplate>

       <LayoutTemplate>
        <table id="itemPlaceholderContainer" runat="server" Class = "table table-hover" >
            <tr runat="server" style="">
                <th runat="server">View</th>
                <th runat="server">HotelName</th>
                <th runat="server">City</th>
                <th runat="server">Province</th>
                <th runat="server">Description</th>
            </tr>
            <tr id="itemPlaceholder" runat="server">
            </tr>
         </table>
       </LayoutTemplate>
    </asp:ListView>

If you look close, we have the FIRST "tr" (table row) that is the repeating rows.

I then added that extra row - gave it a col span = 5 (so it takes up one row

So the code that drives this. Including the + button. And if you hit + again, then I collapse (hide the gridview).

So, code looks like this:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    If Not IsPostBack Then
        LoadGrid()
    End If

End Sub

Sub LoadGrid()

    Dim strSQL As String

    strSQL = "SELECT * FROM tblHotels ORDER BY HotelName"

    Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST4))

        cmdSQL.Connection.Open()

        ListView1.DataSource = cmdSQL.ExecuteReader
        ListView1.DataBind()

    End Using

End Sub

So that displays our grid. of course we have the "+" button, and that simply loads up the grid for the given row, and turns the style (display) = normal.

that code is this:

Protected Sub ListView1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListView1.SelectedIndexChanged

    Dim gVR As ListViewDataItem = ListView1.Items(ListView1.SelectedIndex)
    Dim gChild As GridView = gVR.FindControl("GridView2")   ' pluck out the grid for this row

    If gChild.Style("display") = "normal" Then
        ' if grid is already display, then hide it, and exit
        gChild.Style("display") = "none"
        Exit Sub
    End If

    gChild.Style("display") = "normal"
    Dim HotelPK As String = ListView1.DataKeys(gVR.DataItemIndex).Item("ID")

    Dim strSQL As String
    strSQL = "SELECT * from People where hotel_id = " & HotelPK

    Using cmdSQL As New SqlCommand(strSQL, New SqlConnection(My.Settings.TEST4))

        cmdSQL.Connection.Open()
        gChild.DataSource = cmdSQL.ExecuteReader
        gChild.DataBind()

    End Using

End Sub

Protected Sub ListView1_SelectedIndexChanging(sender As Object, e As ListViewSelectEventArgs) Handles ListView1.SelectedIndexChanging

End Sub

Not a lot of code, and we get a great looking drill down, and even toggle to show, hide.

I really suggest you move this to a ListView for the parent. The child can remain a gridView, or just about anything else.

And notice how MUCH nicer the listView is - you can just drag + drop in regular controls - you do NOT need to use Templated columns over and over and over like you do with GridView.

for most simple grids - or ones with not too much customing, then GridView is quite nice.

But, when you have LOTS of custom controls and you need a LOT of fancy things, and more felxiblity? Then you have to get the big guns out and start using the ListView.

A listview tends to be a "wee bit" more markup to start out with, but since it has near un-limited flexbility, then for a complex cool and amazing grid control, then listView is much more up to the challenge, and has quite a bit more flexlbility in layout.

and the one big feature of the ListView is that ability to have multiple ROWS of layout for one data record - this tends to be REALLY hard with GridView. ListView lets you have "more" then one row of details for each reocrd, and has that valuable colspan (and it even have rowspan).

So for just ONE row of controls, then GridView is fine, but the instant you want more then JUST columns of data, then ListView is the ticket here.

Edit: Add row edit to this child grid

Ok, so we have this code to load up our main List view

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    If Not IsPostBack Then
        LoadGrid()
    End If

End Sub

Sub LoadGrid()
    Dim strSQL As String

    strSQL = "SELECT * FROM tblHotels WHERE ID in (select hotel_Id from People)
             ORDER BY HotelName"

    ListView1.DataSource = MyRst(strSQL)
    ListView1.DataBind()

End Sub

And we have our expand + show child gv as this code:

Protected Sub cmdView_Click(sender As Object, e As EventArgs)

    Dim cmd As Button = sender

    'Dim gVR As ListViewDataItem = ListView1.Items(ListView1.SelectedIndex)
    Dim gVR As ListViewDataItem = cmd.Parent
    Dim gChild As GridView = gVR.FindControl("GridView2")   ' pluck out the grid for this row

    If gChild.Style("display") = "normal" Then
        ' if grid is already display, then hide it, and exit
        gChild.Style("display") = "none"
        Exit Sub
    End If

    gChild.Style("display") = "normal"
    Dim HotelPK As String = ListView1.DataKeys(gVR.DataItemIndex).Item("ID")

    ' only re-load if never loaded (can't re-load else blow out check boxes

    If gChild.Rows.Count = 0 Then
        Dim strSQL As String
        strSQL = "SELECT * from People where hotel_id = " & HotelPK
        gChild.DataSource = MyRst(strSQL)
        gChild.DataBind()

    End If

End Sub

Ok, so now we need to add to the child grid a edit button.

We can drop in a plane jane asp.net button. But I going to drop in a LinkButton - since they can with ease ALSO use bootstrap icons - they look better.

So, in our child GV markup, we add this:

                        <asp:BoundField DataField="City" HeaderText="City"           />
                        <asp:TemplateField HeaderText="Edit" HeaderStyle-Width="140px"
                            ItemStyle-HorizontalAlign="Center" >
                            <ItemTemplate>

                    <asp:LinkButton ID="cmdEdit" runat="server" CssClass="btn btn-default"
                        OnClick="cmdEdit_Click">
                    <span aria-hidden="true" class="glyphicon glyphicon-edit"></span>
                    </asp:LinkButton>

                    <asp:LinkButton ID="cmdDelete" runat="server" CssClass="btn btn-default"
                        style="margin-left:8px" >
                    <span aria-hidden="true" class="glyphicon glyphicon-trash"></span>
                        
                    </asp:LinkButton>

                </ItemTemplate>
            </asp:TemplateField>
           </Columns>
         </asp:GridView>

So, I just dropped in two buttons - and I stuff them BOTH into the one template column. So, what we have so far is now this:

(I expand a row)

enter image description here

So, we have a edit button, and a delete button.

So, we need to build a "div" for the pop up. That just some simple markup in a div that lets us edit the row.

This div can be placed below the List view - it is outside of both the LV and the nested LV - it is just plane jane 100% markup.

I have this:

<div id="myedit" style="float:left;width:20%">
    <div style="text-align:right">
        <p>First Name :<asp:TextBox ID="txtFirstname" runat="server" Width="150"></asp:TextBox> </p>
        <p>Last Name :<asp:TextBox ID="txtLastname" runat="server" Width="150"></asp:TextBox> </p>
        <p>City  :<asp:TextBox ID="txtCity" runat="server" Width="150"></asp:TextBox> </p>
        <p>Active :<asp:CheckBox ID="Active" runat="server" Width="150"></asp:CheckBox> </p>
        <br />
    </div>
    <div style="text-align:right">
        Notes :
        <asp:TextBox ID="txtNotes" runat="server" Width="240px" TextMode="MultiLine" Height="71px"></asp:TextBox> <br />
        <br />
        <asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn" Width="90px"
             />
        <asp:Button ID="cmdCancel" runat="server" Text="Cancel" cssclass="btn" Width="90px"
          OnClientClick="$('#myedit').dialog('close');return false;" style="margin-left:8px"  />
    </div>
</div>

And when rendered on the page, it looks like this:

enter image description here

So, the first challenge? We need a dialog system. There are BOATLOADS. But, two most common are bootstrap, and jQuery.UI.

I find the bootstrap dialogs LOOK really nice. but they are a pain to work with.

So, we going to bite the bullet here. You no doubt are using and have jQuery in your web site - (don't they all now???).

So, you have to add and adopt jQuery.UI. It is a small js library, but I HIGH recommend you adopt it - it just works and allows you to now introduce popup dialogs. This is REALLY nice - and we even use for the delete button (confirm dialog).

So, lay out the div for the one row edit.

We THEN hide the div with style="display:none".

And we have ONE client side script routine - this routine ONLY job is to pop that dialog.

So, we now have this:

 <div id="myedit" style="float:left;width:20%;display:none">
    <div style="text-align:right">
        <p>First Name :<asp:TextBox ID="txtFirstname" runat="server" Width="150"></asp:TextBox> </p>
        <p>Last Name :<asp:TextBox ID="txtLastname" runat="server" Width="150"></asp:TextBox> </p>
        <p>City  :<asp:TextBox ID="txtCity" runat="server" Width="150"></asp:TextBox> </p>
        <p>Active :<asp:CheckBox ID="Active" runat="server" Width="150"></asp:CheckBox> </p>
        <br />
    </div>
    <div style="text-align:right">
        Notes :
        <asp:TextBox ID="txtNotes" runat="server" Width="240px" TextMode="MultiLine" Height="71px"></asp:TextBox> <br />
        <br />
        <asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn" Width="90px"
             />
        <asp:Button ID="cmdCancel" runat="server" Text="Cancel" cssclass="btn" Width="90px"
          OnClientClick="$('#myedit').dialog('close');return false;" style="margin-left:8px"  />
    </div>
</div>
        <script>
            function MyEdit() {
                // pop the div dialog to edit
                var myDialog = $("#myedit");
                myDialog.dialog({
                    modal: true,
                    width: "280px",
                    resizable: false,
                    appendTo: "form",
                    autoOpen: false
                });
                myDialog.dialog('open');
            }
        </script>

So, we add jQuery.ui to this page. My setup looks like this:

<head runat="server">
<title></title>

<script src="Scripts/jquery-1.12.4.js"></script>
<script src="Scripts/bootstrap.js"></script>
<link href="Content/bootstrap.css" rel="stylesheet" />

<link href="Content/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="Scripts/jquery-ui-1.12.1.js"></script>

Now, we COULD adopt a bunch of fancy pants client side code. But to keep this simple?

Everything ELSE from now on is CLEAN EASY pure server side code. I suppose over time, you can ajax the dialog - introduce some web method calls - but it can be quite a bit of extra work - and we don't need to do that.

Ok, so first part:

We need to wire up the event click button in the GV "edit" button. We can't just simple double click on the button to create a click event. (because it is nested in lv and gv). So, from markup, for the edit button we go to markup, and type in on-click, and NOTE VERY VERY cool how we are offered to create that simple code behind click event.

eg this:

enter image description here

So, choose create new event - it don't seem like anything occures, but flip to code behind, and we have our code behind click stub.

In this edit button?

We get the gv row, pull data from database. shove values into our cool edit div, and then pop the dialog.

that code is now this:

Protected Sub cmdEdit_Click(sender As Object, e As EventArgs)
    ' nested grid eit button.

    Dim cmdEdit As LinkButton = sender
    Dim gRow As GridViewRow = cmdEdit.NamingContainer
    Dim GV As GridView = gRow.NamingContainer
    Dim PkID As Integer = GV.DataKeys(gRow.RowIndex).Item("ID")

    Dim strSQL As String = "SELECT * from People WHERE ID = " & PkID

    Dim rstData As DataTable = MyRst(strSQL)

    ' load up our edit div
    With rstData.Rows(0)
        txtFirstname.Text = .Item("FirstName").ToString
        txtLastname.Text = .Item("LastName").ToString
        txtCity.Text = .Item("City").ToString
        If IsDBNull(.Item("Active")) Then
            Active.Checked = False
        Else
            Active.Checked = .Item("Active")
        End If
        txtNotes.Text = .Item("Notes").ToString
    End With

    Session("RowPK") = PkID
    ' save current list view row
    Dim LV As ListViewItem = GV.NamingContainer
    Session("LvRow") = LV.DataItemIndex

    ' now pop the dialog
    Page.ClientScript.RegisterStartupScript(Me.GetType(),
                                            "My pop", "MyEdit()", True)

End Sub

Now not a lot of code - but FULL OF HUGE TIPS AND TRICKS!!!

Note how i grab the gv row. note how I get the PK id. These are really slick tricks.

So we pull the one data row, shove into div, and then last thing we do is setup the page to run our cool little div pop dialog.

The results are now this:

enter image description here

Now, for the cancel button (do nothing), we don't need any real code - we just CLOSE the dialog.

So that button is this:

 <asp:Button ID="cmdCancel" runat="server" Text="Cancel" cssclass="btn" Width="90px"
 OnClientClick="$('#myedit').dialog('close');return false;" style="margin-left:8px"  />

But, our save button? We have to take the text boxes - shove back to database.

So, save button not only has to save the data, but when we close the dialog, we need to re-plot (re-load) the gv - in fact that cost us some extra code - but we need it.

So, the save button will:
Pull text boxes back into table. 
Save send table back to database
re-load re-fresh gv

that code is this:

 Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click

    Dim strSQL = "SELECT * FROM People where ID = " & Session("RowPK")
    Dim rstData As DataTable = MyRst(strSQL)

    With rstData.Rows(0)
        .Item("FirstName") = txtFirstname.Text
        .Item("LastName") = txtLastname.Text
        .Item("City") = txtCity.Text
        .Item("Active") = Active.Checked
        txtNotes.Text = .Item("Notes").ToString
    End With

    ' save to database
    Using conn As New SqlConnection(My.Settings.TEST4)
        Using cmdSQL As New SqlCommand(strSQL, conn)
            Dim da As New SqlDataAdapter(cmdSQL)
            Dim daU As New SqlCommandBuilder(da)
            conn.Open()
            da.Update(rstData)
        End Using
    End Using

    ' since save button does a post back? 
    ' then dialog will collpase - no need to close.

    ' we now need to re-load (refrsh) the GV to display
    ' changes
    Dim LVitem As ListViewItem = ListView1.Items(Session("LvRow"))
    ' get Gird from this row
    Dim GV As GridView = LVitem.FindControl("GridView2")

    Dim HOtelPk As Integer = rstData.Rows(0).Item("Hotel_id")
    strSQL = "SELECT * from People where hotel_id = " & HOtelPk
    Dim rstGV As DataTable = MyRst(strSQL)
    GV.DataSource = rstGV
    GV.DataBind()

End Sub

Now NOT TOO long - but pushing the limits for a screen of code!!!

Note again the slick approach to getting the GV row - we saved into session on edit click the LV row - we could again use a hidden field in the page - so you have several approaches you can use - depends on your current needs.

ok. That is about it!!!

Now, the delete button? We have this:

Again, to be fair - having to re-plot the Gv costs code.

but, not too bad:

Protected Sub cmdDelete_Click(sender As Object, e As EventArgs)
    ' delete this gv row

    Dim cmdDel As LinkButton = sender
    Dim gRow As GridViewRow = cmdDel.NamingContainer
    Dim GV As GridView = gRow.NamingContainer
    Dim PkID As Integer = GV.DataKeys(gRow.RowIndex).Item("ID")

    Dim LVrow As ListViewItem = GV.NamingContainer
    Dim ParentPK As Integer = ListView1.DataKeys(LVrow.DataItemIndex).Item("ID")

    Dim strSQL As String = "DELETE FROM People WHERE ID = " & PkID

    Using conn As New SqlConnection(My.Settings.TEST4)
        Using cmdSQL As New SqlCommand(strSQL, conn)
            conn.Open()
            cmdSQL.ExecuteNonQuery()
        End Using
    End Using

    ' refresh grid

    Dim rstData As DataTable =
        MyRst("SELECT * FROM People WHERE Hotel_id = " & ParentPK)
    GV.DataSource = rstData
    GV.DataBind()

End Sub

And since we REALLY have that cool dialog system? Then lets pop a cool confirm dialog. (we don't have to chance the above delete code for this.

Edit2: The add button at bottom of grid

Ok, so we have a edit, but now lets setup a add button.

We have to drop this button below the Grid, and we where hide/show the grid.

So, we modify the GV - we now going to hide/show the ROW!!!

So, we have this:

          <td id="mygridview" colspan="5" runat="server" style="display:none">

               <asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" 
                 DataKeyNames="ID" CssClass="table table-hover" style="margin-left:3%;width:97%"  >
                    <Columns>
                        <asp:BoundField DataField="Firstname" HeaderText="Firstname" />
                        <asp:BoundField DataField="LastName" HeaderText="LastName"   />
                        <asp:BoundField DataField="City" HeaderText="City"           />
                        <asp:TemplateField HeaderText="Edit" HeaderStyle-Width="140px"
                            ItemStyle-HorizontalAlign="Center" >
                            <ItemTemplate>

                    <asp:LinkButton ID="cmdEdit" runat="server" CssClass="btn btn-default"
                        OnClick="cmdEdit_Click">
                    <span aria-hidden="true" class="glyphicon glyphicon-edit"></span>
                    </asp:LinkButton>

                    <asp:LinkButton ID="cmdDelete" runat="server" CssClass="btn btn-default"
                        OnClientClick="return delconfirm(this);"
                        OnClick="cmdDelete_Click"

                        style="margin-left:8px" >
                    <span aria-hidden="true" class="glyphicon glyphicon-trash"></span>
                        
                    </asp:LinkButton>

                </ItemTemplate>
            </asp:TemplateField>
           </Columns>
         </asp:GridView>
        
         <asp:Button ID="cmdAddRow" runat="server" Text="+"
             style="float:right;margin-right:5px" Width="40px"
             OnClick="cmdAddRow_Click"
             
             />
            </td>

So, we have to update our expand button code - sorry - but we have MORE then GV to show hide. So that code becomes this:

Protected Sub cmdView_Click(sender As Object, e As EventArgs)

    Dim cmd As Button = sender

    'Dim gVR As ListViewDataItem = ListView1.Items(ListView1.SelectedIndex)
    Dim gVR As ListViewDataItem = cmd.Parent
    Dim gChild As GridView = gVR.FindControl("GridView2")   ' pluck out the grid for this row
    Dim MyDiv As HtmlTableCell = gVR.FindControl("mygridview")

    If MyDiv.Style("display") = "normal" Then
        ' if grid is already display, then hide it, and exit
        MyDiv.Style("display") = "none"
        Exit Sub
    End If

    MyDiv.Style("display") = "normal"

    Dim HotelPK As String = ListView1.DataKeys(gVR.DataItemIndex).Item("ID")

    ' only re-load if never loaded (can't re-load else blow out check boxes

    If gChild.Rows.Count = 0 Then
        Dim strSQL As String
        strSQL = "SELECT * from People where hotel_id = " & HotelPK
        gChild.DataSource = MyRst(strSQL)
        gChild.DataBind()

    End If

End Sub

Not much of a change - but we hide / show the row, since we now have GV and button.

That now looks like this:

enter image description here

And our add button click code is now this:

Protected Sub cmdAddRow_Click(sender As Object, e As EventArgs)

    Dim cmdAdd As Button = sender
    Dim LVrow As ListViewItem = cmdAdd.NamingContainer

    ' get PK of parent row
    Dim ParentPK As Integer = ListView1.DataKeys(LVrow.DataItemIndex).Item("ID")

    ' clear edit div
    txtFirstname.Text = ""
    txtLastname.Text = ""
    txtCity.Text = "Edmonton"
    Active.Checked = True
    txtNotes.Text = ""

    Session("LvRow") = LVrow.DataItemIndex
    Session("RowPK") = 0

    ' now pop the Edit dialog
    Page.ClientScript.RegisterStartupScript(Me.GetType(),
                                            "My pop", "MyEdit()", True)

End Sub

And now because we have BOTH edit and add? Well, then our save routine needs a wee bit of change.

(so it can handle BOTH edit/save, and now new record)

So, we tweak it a bit like this:

Protected Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click

    ' if new row - add it!!!
    Dim RowPK As Integer = Session("RowPK")

    Dim strSQL = "SELECT * FROM People where ID = " & Session("RowPK")
    Dim rstData As DataTable = MyRst(strSQL)

    Dim LVitem As ListViewItem = ListView1.Items(Session("LvRow"))
    ' get Gird from this row
    Dim GV As GridView = LVitem.FindControl("GridView2")

    Dim HOtelPk As Integer = ListView1.DataKeys(LVitem.DataItemIndex).Item("ID")

    Dim OneRow As DataRow

    If rstData.Rows.Count = 0 Then
        OneRow = rstData.NewRow
        OneRow("Hotel_ID") = HOtelPk
        rstData.Rows.Add(OneRow)
    Else
        OneRow = rstData.Rows(0)
    End If

    OneRow("FirstName") = txtFirstname.Text
    OneRow("LastName") = txtLastname.Text
    OneRow("City") = txtCity.Text
    OneRow("Active") = Active.Checked
    OneRow("Notes") = txtNotes.Text

    ' save to database
    Using conn As New SqlConnection(My.Settings.TEST4)
        Using cmdSQL As New SqlCommand(strSQL, conn)
            Dim da As New SqlDataAdapter(cmdSQL)
            Dim daU As New SqlCommandBuilder(da)
            conn.Open()
            da.Update(rstData)
        End Using
    End Using

    ' since save button does a post back? 
    ' then dialog will collpase - no need to close.

    ' we now need to re-load (refrsh) the GV to display
    ' changes

    strSQL = "SELECT * from People where hotel_id = " & HOtelPk
    Dim rstGV As DataTable = MyRst(strSQL)
    GV.DataSource = rstGV
    GV.DataBind()

End Sub

Almost the same!!! - but we setup the parent PK (well FK to this row), and we create a new row, or grab the existing.

Last but not least? I have a few places where I need to pull data. I was wearing out my keyboard. So I have this helper routine to return a datatable.

It is a handy rountine. Remember, only use string concantation for PK values from datakeys - NEVER USE THIS for user input from a web page.

Public Function MyRst(strSQL As String) As DataTable

    Dim rstData As New DataTable
    Using conn As New SqlConnection(My.Settings.TEST4)
        Using cmdSQL As New SqlCommand(strSQL, conn)
            conn.Open()
            rstData.Load(cmdSQL.ExecuteReader)
            rstData.TableName = strSQL
        End Using
    End Using

    Return rstData
End Function
Sign up to request clarification or add additional context in comments.

12 Comments

I decided to go your way. Much cleaner and the AutoCompleteExtender works well without the hidden div.
Glad this works. I mean, I whole cool grid expand button - that Ondinex changed event has what 12 lines of code? and you get this great UI. There is a lesson here: if your writing too much code and fighting the UI? Then try another road. That above code to load, display and expand is not only simple code - but the markup was also quite limited, and not pages of huge masses of messy markup. LESS code and LESS markup is the better choice here.
i used this approach suggested by you. Now i need to create a click event on the girdview row for the user to be able to add more rows. How do i do this?
Well, would not such a add row button would be placed below the grid? In other words, you can with great ease drop in a button to the GV, and say when you click on it view data from that row (maybe a cool popup). However, I don't think we want a repeating "add" button that appears on each row to add a row? ( I mean, with 5 rows, we would have 5 add buttons? Should not just a simple button right below the GV make more sense - say called "Add new". But to answer this question, yes a simple button inside of the GV (or better right below GV) is easy, and would be only a few lines of code.
However, do we pop a dialog for entry of that new GV row, or do you want to turn that existing GV to allow edits? If yes, then we would want a save button, and a add row button below that GV then, right?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.