Page 8 of 22 - Chapter 14
The MusicMad Shopping Basket (Part 1)
The MusicMad shopping basket relies exclusively on cookies, in particular
the basket cookie which we set. Given we have server-side programming available
to us you may wonder why we still use cookies stored on the client machine
for storing our basket's contents instead of say, session variables or the
database.
The main reason is scalability. Storing any data on the server consumes the
finite server resources available. On a popular web site at peak times user
sessions could run into hundreds of thousands, maybe more. Session variables
consume resources until the user's session times out, which is 20 minutes
by default. Using a database to store basket contents is a possibility but
again your database could grow very large, even if just temporarily. It also
requires database connection each time a user accesses their basket which,
again, is costly in terms of server memory and processing time. It's likely
the user's machine has more than sufficient resources to cope with our shopping
basket's storage and processing, so it makes sense to distribute responsibility
here.
Adding Items to the Basket
When we wrote the code which dynamically creates a page with
a list of products, we also put an <A>
tag for each item with Add to
Basket as the link's text and AddToBasket.asp
as its href property.
We also appended ItemId, the number
of CDs wanted (by default 1), ArtistName, record Title
and Price per item to the end of the <A>
tag's href. We can now retrieve those values
in our server-side ASP script.
The values passed in the URL were also escaped into a URL-encoded form. Effectively,
this means that non-alphabetic characters were converted to a hexadecimal
format. For example, a space would become %20
if there were any in the link. So, the first thing we must do is unescape those values and split them up into
the individual data values for ItemId, ArtistName etc. We use the ItemId to check there is not already one of
those items already in the basket. If there is then we get the current quantity
in the basket and add one to it. Finally, we use the setItemCookie
function that's been made available by including our server-side global module
at the top of the page. It's perhaps worth mentioning here that server-side
include directives cannot have include directives
in them to other files, precluding the errors inherent in an 'include chain'.
This is managed as ASP has a one-step preprocessing stage by any server-side
code is executed and the include
statement is dealt with in this stage.
<!--#include file="ServerSideGlobalDef.inc"-->
<%
// decode query string from HTTP form
var sQueryString = unescape(Request.QueryString);
var sBasketCookie = new String(Request.Cookies("Basket"));
// String is in form
// IDItemId&Qty&ArtistName&Description&Price
var NewItemData = sQueryString.split("&");
if (sBasketCookie.indexOf(NewItemData[0] + "&") >=
0 )
{
var ExistItemData = getItemFromCookie(NewItemData[0],sBasketCookie);
NewItemData[1] =parseInt(NewItemData[1])+parseInt(ExistItemData[1]);
}
Response.Cookies("Basket")=setItemToCookie(NewItemData,sBasketCookie);
%> |
We complete the page by adding a message informing the user what has just
been successfully added to their basket.
It's also nice, from a user interface point of view, to give them the option
to view their basket or proceed to the checkout by putting a form with two
buttons on the page. The event handler code has been added to the HTML tags
and simply replaces the current page of the main frame with the relevant
pages for viewing the basket, which we create next, and the checkout page
which will be created later.
<HTML>
<BODY>
<BR><BR><BR>
<P align="center">
<FONT FACE="Comic Sans MS" SIZE="3">
1 copy of <STRONG><%= NewItemData[3] %></STRONG><BR>
by <STRONG><%= NewItemData[2] %></STRONG>
has been added to your shopping basket
</FONT>
</P>
<DIV align="center">
<FORM ACTION="" method="POST">
<INPUT TYPE="Button" NAME="cmdCheckout" VALUE="Proceed
to Checkout" _
onClick="window.location.replace('checkout_frame.htm')">
<INPUT TYPE="Button" NAME="cmdBasket" VALUE="View
Basket" _
onClick="window.location.replace('viewbasket.asp')">
</FORM>
</DIV>
</BODY>
</HTML> |
Save the page as AddToBasket.asp
Viewing the Basket's Contents
The screenshot above shows what we are aiming for as regards
the contents and layout of the basket. As well as showing each item, the quantity,
price and total price we also have a summary of
the order cost at the end.
Before we create the page that views the basket, we are going to create another
server-side include file: Basket.inc.
As you will see later, viewing the contents of the basket is something we
need to do in a number of different places on the site, so by creating an
include file we keep the code in one place making debugging and maintenance
easier.
The basket include file retrieves
each item from the basket cookie used to store the goods and produces a table
listing the item's artist, description, quantity selected, price per item
and cost of the total quantity of that item in the basket. Finally at the
end of the table it produces a sub total of the cost of all the items, cost
of delivery and final cost.
The include file uses a variable bReadOnly
which we define and set outside the include file. If bReadOnly
is false then in each item row in the table, the
quantity of each item is displayed inside a text box. Below the table is a button
which when clicked updates the quantities based on the values in the quantity
boxes. If the user wishes to remove an item from the basket they enter 0 in its quantity text box and hit the Update Quantities button.
If bReadOnly has been
set to true then quantities are displayed as
plain text and there is no Update Quantities
button. This gives us a method of displaying a non-updateable summary of basket
items at checkout.
Our first task is to make sure
the user is viewing the very latest details and not a stale cached version
of the basket. We can accomplish this by setting the Response.Expires
property to -1, as we did in browse.asp, to ensure the page expires as soon
as it is loaded by the browser.
Following
that is a JavaScript form validation function, checkQtys. This function loops through each
element in the basket's form and if the element is a text box, (i.e. its type
property is text) we check that the contents are
a valid whole number. It's worth mentioning here that the <FORM> tag is actually written outside
the include file as this makes it easier to set parameters such as the form's
action, method, name and events. This makes our whole code a little more adaptable
to different situations. The validation routine does not need to know the
name of the form it's validating because we pass the form object itself as the function's parameter.
If parsing
the text box's value to an integer produces NaN, we know that the user has entered some
invalid value. We react to this with a warning message, then set focus to
the offending element and select the text inside it (just to make things really
clear to the user). Also, false is returned
from the function and is important if this routine is used as the onSubmit event handler, as returning false cancels the submit event.
<% Response.Expires = -1; %>
<SCRIPT LANGUAGE="JavaScript">
function checkQtys(theForm)
{
for (var iElement = 0; iElement < theForm.length;iElement++)
{
if (theForm[iElement].TYPE == "text")
{
if (isNaN(parseInt(theForm[iElement].VALUE)) || _
(parseInt(theForm[iElement].VALUE) < 0))
{
alert("The quantity you have entered is invalid\n _
Only whole numbers are valid in this box");
theForm[iElement].focus();
theForm[iElement].select();
return false;
}
}
}
return true;
}
</SCRIPT> |
Following the validation script we define the table and its headers and then
start looping through the basket's contents. As we saw above, each item in
the basket cookie is in the format:
ItemId&Qty&ArtistName&Title&Price£&