Master/Detail Editing - DetailsView

Record editing with a single DetailsView or FormView works fine for small database tables requiring few pages to navigate. For large recordsets, though, it may be difficult to locate particular records by paging through them sequentially. What is required is a table of contents to quickly locate records of interest, and a form for editing those that are found. These requirements are easily provided in a master/detail setup. In the following example, a GridView presents an index to all records in the BooksDB.mdb database; a linked DetailsView gives editing access to individual records.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Master
IDTitle
DB111 Oracle Database
DB222 Databases in Depth
DB333 Database Processing
DB444 Access Database Design
DB555 SQL Server 2005
GR111 Adobe Photoshop CS2
GR222 Learning Web Design
GR333 Macromedia Flash Professional
GR444 Digital Photographer Handbook
GR555 Creating Motion Graphics
HW111 How Computers Work
HW222 Upgrading and Repairing PCs
123
Book Detail
 
ID: DB111
Type: Database
Title: Oracle Database
Author: K. Loney
Description:
Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.
Price: $69.99
Qty: 10
Sale:

Figure 9-24. Master/detail record editing.

Coding the GridView

Code for the master GridView and its AccessDataSource are shown below. A TemplateField containing <asp:LinkButton> controls both displays BookIDs and configures them as command links for selecting books. The CommandName for the LinkButtons is "Select" to tie BookIDs to the SelectCommand property of the AccessDataSource for the DetailsView.

<asp:AccessDataSource id="MasterSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT BookID, BookTitle FROM Books ORDER BY BookID"/>

<asp:GridView id="GridView" DataSourceID="MasterSource" Runat="Server"
  DataKeyNames="BookID"
  AutoGenerateColumns="False"
  Caption="<b>Book Master</b>"
  CaptionAlign="Left"
  AllowPaging="True"
  PageSize="12"
  SelectedIndex="0"
  SelectedRowStyle-BackColor="#E9E9E9"
  HeaderStyle-BackColor="#E0E0E0"
  Style="float:left; margin-bottom:200px">
  
  <Columns>
  
  <asp:TemplateField 
    HeaderText="ID" 
    HeaderStyle-Width="40" 
    HeaderStyle-Font-Size="10pt" 
    ItemStyle-Font-Size="10pt">
    <ItemTemplate>
      <asp:LinkButton CommandName="Select" Runat="Server"
      Text='<%# Eval("BookID") %>'/>
    </ItemTemplate>
  </asp:TemplateField>
  
  <asp:BoundField	DataField="BookTitle" 
    HeaderText="Title" 
    HeaderStyle-Width="180"
    HeaderStyle-Font-Size="10pt" 
    Itemstyle-Font-Size="10pt"/>
  
  </Columns>

</asp:GridView>
Listing 9-38. Coding a master GridView.

Coding the DetailsView

The DetailsView employs an AccessDataSource whose SelectCommand is linked to the GridView selection through its SelectParameters. The GridView's SelectedValue becomes the @BookID parameter for displaying the chosen record. A second AccessDataSource fills DropDownLists of book types during updating and inserting operations.

<asp:AccessDataSource id="DetailsSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"
  
  SelectCommand="SELECT * FROM Books WHERE BookID = @BookID"
  
  InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, 
                 BoodDescription, BookPrice, BookQty, BookSale) 
                 VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, 
                 @BoodDescription, @BookPrice, @BookQty, @BookSale)"

  UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle,
                 BookAuthor=@BookAuthor, BoodDescription=@BoodDescription, 
                 BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale 
                 WHERE BookID=@BookID"
>
  <SelectParameters>
    <asp:ControlParameter ControlID="GridView" Name="BookID" 
    PropertyName="SelectedValue"/>
  </SelectParameters>
	
</asp:AccessDataSource>

<asp:AccessDataSource id="TypeSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>
Listing 9-39. Linking a DetailsView to a master GridView via AccessDataSource parameters.

Code for the DetailsView is shown below. Editing buttons are supplied through a TemplateField containing Button controls configured as command buttons. Also, a set of delete confirmation buttons appears inside the DetailsView's ItemTemplate.

<asp:DetailsView id="DetailsView" Runat="Server"
  DataSourceID="DetailsSource"
  AutoGenerateRows="False"
  DataKeyNames="BookID"
  OnItemInserting="Validate_Insert_Data"
  OnItemInserted="Display_Insert_Msg"
  OnItemUpdating="Validate_Update_Data"
  OnItemUpdated="Display_Update_Msg"
  OnItemDeleting="Confirm_Delete"
  OnItemCommand="Get_Command"
  Caption="<b>Book Detail</b>"
  CaptionAlign="Left"
    BorderStyle="Outset"
    BorderWidth="1"
    CellPadding="3"
    GridLines="None"
    RowStyle-Font-Size="10pt"
    RowStyle-VerticalAlign="Top">

  <Fields>
  
  <asp:TemplateField
    HeaderStyle-BackColor="#E0E0E0" 
    HeaderStyle-Font-Bold="True" 
    HeaderStyle-HorizontalAlign="Right" 
    ItemStyle-VerticalAlign="Top"
    ItemStyle-BackColor="#E0E0E0" 
    ItemStyle-ForeColor="#FF0000">
    <ItemTemplate>
      <asp:Button CommandName="Edit" Text="Edit" Runat="Server"
        Font-Size="8pt" Width="45"/>
      <asp:Button CommandName="New" Text="New" Runat="Server"
        Font-Size="8pt" Width="45"/>
      <asp:Button CommandName="Delete" Text="Delete" Runat="Server"
        Font-Size="8pt" Width="45"/>
      <asp:Label id="ConfirmDelete" Visible="False" EnableViewState="False" 
      Runat="Server">
        <asp:Label Text="<br/>Delete this record? " ForeColor="Red" 
          EnableViewState="False" Runat="Server"/>
        <asp:Button Text="Yes" CommandName="Yes" Runat="Server"
          Font-Size="7pt" Width="30px"/>
        <asp:Button Text="No" CommandName="No" Runat="Server"
          Font-Size="7pt" Width="30px"/>
      </asp:Label>
    </ItemTemplate>
    <EditItemTemplate>
      <asp:Button CommandName="Update" Text="Update" Runat="Server"
        Font-Size="8pt" Width="45"/>
      <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server"
        Font-Size="8pt" Width="45"/>
    </EditItemTemplate>
    <InsertItemTemplate>
      <asp:Button CommandName="Insert" Text="Insert" Runat="Server"
        Font-Size="8pt" Width="45"/>
      <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server"
        Font-Size="8pt" Width="45"/>
    </InsertItemTemplate>
  </asp:TemplateField>
  
  <asp:BoundField ReadOnly="True" 
    DataField="BookID"
    HeaderText="ID: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>
  
  <asp:TemplateField 
    HeaderText="Type: " 
    HeaderStyle-BackColor="#E0E0E0"
    HeaderStyle-Font-Bold="True">
    <ItemTemplate>
      <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/>
    </ItemTemplate>
    <EditItemTemplate>
      <asp:DropDownList id="EditType" Runat="Server"
        DataSourceID="TypeSource"
        DataTextField="BookType" 
        DataValueField="BookType" 
        SelectedValue='<%# Bind("BookType") %>'/>
    </EditItemTemplate>
  </asp:TemplateField>
  
  <asp:BoundField	
    DataField="BookTitle" 
    HeaderText="Title: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>
  
  <asp:BoundField	
    DataField="BookAuthor" 
    HeaderText="Author: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>	
  
  <asp:TemplateField 
    HeaderText="Description: " 
    HeaderStyle-BackColor="#E0E0E0"
    HeaderStyle-Font-Bold="True">
    <ItemTemplate>
      <asp:Panel Width="210px" Height="75px" BorderWidth="1px" 
      ScrollBars="Auto" BorderColor="#C0C0C0" Runat="Server">
        <asp:Label Font-Size="10pt" Runat="Server"
        Text='<%# Eval("BookDescription") %>'/>
      </asp:Panel>
  </ItemTemplate>
  <EditItemTemplate>
    <asp:TextBox id="EditDescription" 
      Text='<%# Bind("BookDescription") %>' Runat="Server"
      TextMode="MultiLine" Height="75px" Width="210px" 
      Font-Name="Arial" Font-Size="9pt"/>
    </EditItemTemplate>
  </asp:TemplateField>
  
  <asp:BoundField 
    DataField="BookPrice"
    HtmlEncode="False"
    DataFormatString="{0:C}"
    HeaderText="Price: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>
  
  <asp:BoundField 
    DataField="BookQty"
    HtmlEncode="False"
    DataFormatString="{0:D}"
    HeaderText="Qty: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>
  
  <asp:CheckBoxField 
    DataField="BookSale" 
    HeaderText="Sale: "
    HeaderStyle-BackColor="#E0E0E0"	
    HeaderStyle-Font-Bold="True"/>
  
  </Fields>

</asp:DetailsView>

<asp:Label id="EditMSG" EnableViewState="False" ForeColor="Red" 
Runat="Server"/>
Listing 9-40. Code for DetailsView to display records selected from GridView.

Bound fields are used for several data fields, for example the BookPrice field. When this control is placed in edit or insert mode, a default textbox appears for changing or adding the field. To have more control over the size and properties of this entry box, you may wish to use a TemplateField instead, enclosing an <asp:TextBox>. This gives you the option of sizing the textbox, formatting its text, and including the MaxLength property to restrict the number of characters typed. A BoundField is a convenient editing device, but it may not provide the formatting, styling, and data-entry options you seek.

DetailsView Scripting

Scripting for the DetailsView uses similar techniques as before. Click events are trapped by OnItemInserting, OnItemInserted, OnItemUpdating, OnItemUpdated, OnItemDeleting, and OnItemCommand handlers. During inserting and updating, subprograms validate entered data; during deletion, confirmation buttons are presented. All subprograms display appropriate messages about the current editing status.

<SCRIPT Runat="Server">

Sub Validate_Insert_Data (Src As Object, Args As DetailsViewInsertEventArgs)
  
  If Args.Values("BookID") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing BookID"
  End If
  
  If Args.Values("BookTitle") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing title"
  End If
  
  If Args.Values("BookAuthor") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing author"
  End If
  
  If Args.Values("BookDescription") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing description"
  End If	
  
  If Not IsNumeric(Args.Values("BookPrice")) Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Price is not numeric"
  Else
    If Args.Values("BookPrice") < 0 _
    OR Args.Values("BookPrice") > 200 Then
      Args.Cancel = True
      EditMSG.Text = "&bull; Price is out of range"
    End If
  End If
  
  If Not IsNumeric(Args.Values("BookQty")) Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Quantity is not numeric"
  Else
    If Args.Values("BookQty") < 0 Then
      Args.Cancel = True
      EditMSG.Text = "&bull; Quantity is out of range"
    End If
  End If
	
End Sub

Sub Display_Insert_Msg (Src As Object, Args As DetailsViewInsertedEventArgs)

  EditMSG.Text = "&bull; Record " & Args.Values("BookID") & " added"
  GridView.DataBind()
  
End Sub

Sub Validate_Update_Data (Src As Object, Args As DetailsViewUpdateEventArgs)

  If Args.NewValues("BookTitle") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing title"
  End If
  
  If Args.NewValues("BookDescription") = "" Then
    Args.Cancel = True
    EditMSG.Text = "&bull; Missing description"
  End If	
  
  If Not IsNumeric(Args.NewValues("BookPrice")) Then
    Args.Cancel = True
    EditMSG.Text = "&bull;Price is not numeric"
  Else
    If Args.NewValues("BookPrice") < 0 _
    OR Args.NewValues("BookPrice") > 200 Then
      Args.Cancel = True
      EditMSG.Text = "&bull; Price is out of range"
    End If
  End If
  
  If Not IsNumeric(Args.NewValues("BookQty")) Then
    Args.Cancel = True
    EditMSG.Text = "&bull;Quantity is not numeric"
  Else
    If Args.NewValues("BookQty") < 0 Then
      Args.Cancel = True
      EditMSG.Text = "&bull; Quantity is out of range"
    End If
  End If
  
End Sub

Sub Display_Update_Msg (Src As Object, Args As DetailsViewUpdatedEventArgs)

  EditMSG.Text = "&bull; Record " & Args.Keys("BookID") & " updated"
  
End Sub

Sub Confirm_Delete (Src As Object, Args As DetailsViewDeleteEventArgs)
  
  Args.Cancel = True
  Dim ConfirmLabel As Label 
  ConfirmLabel = DetailsView.Rows(Args.RowIndex).FindControl("ConfirmDelete")
  ConfirmLabel.Visible = True
  ViewState("BookID") = Args.Keys("BookID")
  
End Sub

Sub Get_Command (Src As Object, Args As DetailsViewCommandEventArgs)

  If Args.CommandName = "Yes" Then
    DetailsSource.DeleteCommand = "DELETE FROM Books " & _
                                  "WHERE BookID = '" & _
                                  ViewState("BookID") & "'"
    DetailsSource.Delete()
    GridView.DataBind()
    EditMSG.Text = "&bull; Record " & ViewState("BookID") & " deleted"
  End If
  
End Sub

</SCRIPT>
Listing 9-41. Script to validate and update records through master/detail GridView/DetailsView.

You will notice a slight difference in coding from previous scripts to locate the ConfirmDelete Label containing "Yes" and "No" confirmation buttons. In coding this technique for a previous FormView, the control's FindControl("id") method is used. For a DetailsView, however, this method's format is different as shown below.

Sub Confirm_Delete (Src As Object, Args As DetailsViewDeleteEventArgs)
  
  Args.Cancel = True
  Dim ConfirmLabel As Label
  ConfirmLabel = DetailsView.Rows(Args.RowIndex).FindControl("ConfirmDelete")
  ConfirmLabel.Visible = True
  ViewState("BookID") = Args.Keys("BookID")
  
End Sub
Listing 9-42. Finding controls in the first row of a DetailsView.

Whereas a FormView contains a single Row object that is searched for a control, a DetailsView contains multiple Rows, one for each visible row of the control. These rows are indexed beginning with 0, and this index is used to point to the particular row to search to find an enclosed control.

Arguments passed to subprograms when events occur on a row include the RowIndex property for that row. In the current DetailsView, the confirmation buttons and their container Label appear in the same row as the "Delete" button. Therefore, when this button is clicked to call the Confirm_Delete subprogram, this subprogram's Args.RowIndex property points to the same row containing the confirmation Label. This row, then, is searched for the ConfirmDelete Label.

Incidentally, in this particular example, it is known in advance that the confirmation label is on row 0. Therefore, this fixed index can be used to find the Label:

DetailsView.Rows(0).FindControl("ConfirmDelete")

Notice in the example GridView that when a new record is added or an existing record is deleted that the GridView is bound again from its AccessDataSource: GridView.DataBind(). This re-binding ensures that the GridView correctly represents the changed contents of the Books table after records are added and deleted.

A FormView can be easily substituted for the above DetailsView. The one from the previous FormView editing tutorial can be used by linking it to the GridView. Slight script changes are needed for subprogram arguments and for locating embedded controls. Still, it is nearly a matter of swapping one control for another.