The ShopCart.aspx page is visited by clicking on the menu link. It displays the current items in the ShopCart table for this customer. Options are provided to change the quantities ordered for any book or to delete the book from the shopping cart. A button is displayed to continue checkout processing.
The ShopCart.aspx Content Page
Shown below is the code outline for the ShopCart.aspx page including the GridView and its AccessDataSource to present items from the ShopCart table for a customer.
<%@ Page MasterPageFile="eCommerce.master" Language="vb" Debug="True"%> <SCRIPT Runat="Server"> ... </SCRIPT> <asp:Content id="ShopCart" ContentPlaceHolderID="CONTENT" Runat="Server"> <asp:Label Text="Shopping Cart" Font-Size="14pt" ForeColor="#990000" Runat="Server"/><br/> <br/> <table border="0"> <tr> <td><b>Order No: </b></td> <td><asp:Label id="OrderNumberLabel" Runat="Server"/></td> </tr> </table> <asp:Label id="NoItemsMessage" Runat="Server" Text="No items in shopping cart" EnableViewState="False" Visible="False" Width="180" Height="40" BackColor="#E0E0E0" ForeColor="#990000" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="margin-top:10px; padding:15px"/> <asp:Panel Width="550" Runat="Server" ForeColor="#990000" HorizontalAlign="Right"> <asp:Label id="ErrMessage" Text=" " EnableViewState="False" Runat="Server"/> </asp:Panel> <asp:AccessDataSource id="ShopCartSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" OnSelected="Get_Rows" SelectCommand="SELECT * FROM ShopCart WHERE OrderNumber=@OrderNumber" UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID" DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID" > <SelectParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </SelectParameters> <UpdateParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </UpdateParameters> <DeleteParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </DeleteParameters> </asp:AccessDataSource> <asp:GridView id="ShopCartGrid" DataSourceID="ShopCartSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="OrderNumber, BookID" OnRowUpdating="Validate_Quantity" Cellpadding="3" ShowFooter="True" HeaderStyle-BackColor="#990000" HeaderStyle-ForeColor="#FFFFFF" EditRowStyle-BackColor="#E0E0E0" > <Columns> <asp:TemplateField HeaderText="ID" ItemStyle-Width="50"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookID") %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookID") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Title" ItemStyle-Width="200"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookTitle") %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookTitle") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Price" ItemStyle-HorizontalAlign="Right" ItemStyle-Width="60"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Eval("BookPrice")) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Eval("BookPrice")) %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Qty" ItemStyle-Width="40" FooterStyle-HorizontalAlign="Right" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Font-Size="9pt" Style="text-align:right"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="BookQty" Runat="Server" Text='<%# Bind("BookQty") %>' MaxLength="2" Width="25" Height="17" Font-Size="9pt" Style="padding:0px; text-align:right"/> </EditItemTemplate> <FooterTemplate> <asp:Label Text="Shipping" Runat="Server"/><br/> <asp:Label Text="Total" Runat="Server" Font-Bold="True" Width="50" BorderStyle="Solid" BorderWidth="0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Amount" ItemStyle-Width="80" ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </EditItemTemplate> <FooterTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Get_Shipping()) %>'/><br/> <asp:Label id="OrderTotal" Runat="Server" Text='<%# String.Format("{0:C}", Get_Order_Total()) %>' Width="80" Font-Bold="True" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Edit" ItemStyle-Width="75" FooterStyle-BackColor="#E0E0E0"> <ItemTemplate> <asp:Button Text="Edit" CommandName="Edit" Runat="Server" Font-Size="7pt" Width="35"/> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="35"/> </ItemTemplate> <EditItemTemplate> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="35"/> <asp:Button Text="Cancel" CommandName="Cancel" Runat="Server" Font-Size="7pt" Width="35"/> </EditItemTemplate> <FooterTemplate> <asp:Button Text="Checkout »" OnClick="Submit_Form" Runat="Server" Font-Size="8pt" Width="75" Style="margin-top:22px"/> </FooterTemplate> </asp:TemplateField> </Columns> </asp:GridView> </asp:Content>
The "Checkout »" button in the FooterTemplate calls a subprogram named Submit_Form which is explained in the following tutorial. In order to test the current page without receiving an error on this missing subprogram, you may wish to temporarily delete this event handler from the button. It can be replaced after coding the Submit_Form subprogram.
Retrieving Customer Products
An AccessDataSource populates the GridView with all database records with an OrderNumber field matching the Session("OrderNumber") being maintained for this customer. This AccessDataSource, the Label displaying the order number, and the Page_Load script to assigned the number to the Label are shown below. Interaction between these page elements retrieves the appropriate records for display.
<SCRIPT Runat="Server"> Sub Page_Load If Not Page.IsPostBack Then OrderNumberLabel.Text = Session("OrderNumber") End If End Sub ... </SCRIPT> <asp:Label Text="Shopping Cart" Font-Size="14pt" ForeColor="#990000" Runat="Server"/><br/> <br/> <table border="0"> <tr> <td><b>Order No: </b></td> <td><asp:Label id="OrderNumberLabel" Runat="Server"/></td> </tr> </table> <asp:AccessDataSource id="ShopCartSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" OnSelected="Get_Rows" SelectCommand="SELECT * FROM ShopCart WHERE OrderNumber=@OrderNumber" UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID" DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID" > <SelectParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </SelectParameters> <UpdateParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </UpdateParameters> <DeleteParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </DeleteParameters> </asp:AccessDataSource>
The first time the page loads, Session("OrderNumber") is assigned to the OrderNumberLabel for display on the page. This Label also serves as the source for the AccessDataSource's SelectCommand parameter to retrieve the matching records (WHERE OrderNumber=@OrderNumber).
Often, a command parameter is located inside the control that is bound to the data source. Its value is given in a binding expression (<%# Eval(field) %>) where the field name matches the parameter name (@field). In a previous example of master/detail editing with a DetailsView it is described how to indicate command parameters that are outside the control to which the data source is bound. This takes place in the <SelectParameters> section of the AccessDataSource, where an <asp:ControlParameter> identifies the outside control where the parameter value can be found. In the current example, the parameter value is identified as the Text property of the OrderNumberLabel whose value is assigned in the Page_Load subprogram. This Label, then, supplies the @OrderNumber value for selecting records from the ShopCart table. Notice also that this same Label supplies @OrderNumber values for the AccessDataSource's UPDATE and DELETE statments.
Missing Shopping Cart Items
It is possible, of course, that the customer arrives at the shopping cart page without having yet chosen items for purchase. In this case, an empty GridView is not displayed; rather, a message box is displayed indicating that no items appear in the shopping cart.
A determination needs to be made whether there are records in the ShopCart table for this customer. This information is given by the number of rows (records) returned by the AccessDataSource when it issues its SelectCommand. The data source's Selected event can be trapped by an OnSelected event handler to call a subprogram that checks the data source's AffectedRows property. If the number of rows returned by the AccessDataSource is 0, then the message box is displayed. Code for this subprogram and the affected Label are shown below.
Sub Get_Rows (Src As Object, Args As SqlDataSourceStatusEventArgs) If Args.AffectedRows = 0 Then NoItemsMessage.Visible = True End If End Sub ... <asp:Label id="NoItemsMessage" Runat="Server" Text="No items in shopping cart" EnableViewState="False" Visible="False" Width="180" Height="40" BackColor="#E0E0E0" ForeColor="#990000" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="margin-top:10px; padding:15px"/>
The subprogram argument for a data source's event handler is SqlDataSourceStatusEventArgs. This argument's AffectedRows property gives the number of rows returned when a SelectCommand is issued. In this example, if no rows are returnedif this customer does not have any records in the ShopCart tablethe NoItemsMessage Label is made visible. Note that the Label is pre-configured as a boxed display with an enclosed message. The subprogram simply makes it visible in place of the GridView, which is not visible since is has no rows to display.
GridView Display
The GridView displays all records for the customer from the ShopCart table. It also defines an EditItemTemplate for each column. Only for the BookQty column, however, is a TextBox displayed rather than a Label displaying the field value. This is the only value for which editing is permitted. Its binding to the database uses a Bind() method rather than an Eval() method to permit updating its value.
<asp:TemplateField HeaderText="Qty" ItemStyle-Width="40" FooterStyle-HorizontalAlign="Right" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Font-Size="9pt" Style="text-align:right"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="BookQty" Runat="Server" Text='<%# Bind("BookQty") %>' MaxLength="2" Width="25" Height="17" Font-Size="9pt" Style="padding:0px; text-align:right"/> </EditItemTemplate> <FooterTemplate> <asp:Label Text="Shipping" Runat="Server"/><br/> <asp:Label Text="Total" Runat="Server" Font-Bold="True" Width="50" BorderStyle="Solid" BorderWidth="0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField>
The "Amount" column displays calculated values by calling functions to produce line-item amounts. In this column's FooterTemplate, shipping and order total amounts are given by function calls. Code for this column is repeated below along with the script functions producing its values. Notice that a global TotalAmount variable is declared for summarizing individual line-item amounts. Operation of these functions should be familiar from past examples.
Dim TotalAmount As Decimal = 0.00 Function Get_Amount (Price As Decimal, Quantity As Integer) Dim Amount As Decimal Amount = Price * Quantity TotalAmount += Amount Return Amount End Function Function Get_Shipping() Dim Shipping As Decimal = TotalAmount * Application("Shipping") TotalAmount += Shipping Return Shipping End Function Function Get_Order_Total() Return TotalAmount End Function ... <asp:TemplateField HeaderText="Amount" ItemStyle-Width="80" ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </EditItemTemplate> <FooterTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Get_Shipping()) %>'/><br/> <asp:Label id="OrderTotal" Runat="Server" Text='<%# String.Format("{0:C}", Get_Order_Total()) %>' Width="80" Font-Bold="True" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField>
Updating the Quantity Ordered
When a row's "Edit" button is clicked, the row is put in edit mode for changing the quantity ordered for a book. When the "Update" button is clicked, the quantity is updated. First, though, the GridView's OnRowUpdating="Validate_Quantity" event handler calls a subprogram to validated the changed amount.
Sub Validate_Quantity (Src As Object, Args As GridViewUpdateEventArgs) If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True ErrMessage.Text = "Book quantity must be numeric" ElseIf Args.NewValues("BookQty") < 1 Then Args.Cancel = True ErrMessage.Text = "Book quantity is out of range" End If End Sub ... <asp:GridView id="ShopCartGrid" DataSourceID="ShopCartSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="OrderNumber, BookID" OnRowUpdating="Validate_Quantity" ...
The quantity entered is checked for numeric characters and a positive value. If these tests are failed, then updating is cancelled and an error message is written to a message Label. Otherwise, the AccessDataSource's UpdateCommand is issued.
UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID"
Here is a case where it requires two fields to uniquely identify a record in the ShopCart table. When a customer purchases more than one book, there are multiple records with the same OrderNumber. This value needs to be combined with a BookID value to produce a unique key. Recall that when performing updates to a record a GridView needs to identify in its DataKeyNames property the names of the values which serve as record keys. In this case, then, both the OrderNumber and BookID names are identified.
Of course, there is no OrderNumber value inside the GridView; it appears in the external OrderNumberLabel. However, this Label's value is associated with the UPDATE statement's @OrderNumber parameter through the AccessDataSource's <UpdateParameters> entry. In a round-about way, OrderNumber is a data key name even though its value comes from the external OrderNumberLabel. Also, as a key field, BookID must be bound to the GridView with the Eval() method rather than the Bind() method since its value is not permitted to be updated.
After updating takes place, the AccessDataSource is automatically re-bound to the GridView to display the changed quantity and its effect on shopping cart calculations.
Deleting a Shopping Cart Record
When a "Delete" button is clicked, the book on that row of the shopping cart display is deleted from the shopping cart. No intermediate processing is performed; the item is immediately deleted by issuance of the AccessDataSource's DeleteCommand.
DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID"
As in the case for row updating, the @BookID parameter comes from the BookID field of the GridView and the @OrderNumber parameter comes from the OrderNumberLabel as given by the <DeleteParameters> for the AccessDataSource.
After deletion of the record takes place, the AccessDataSource is automatically re-bound to the GridView to display the changed shopping cart contents.