CSCI A348/548
Lecture Notes Fifteen

Spring 2001 (Second semester 2000-2001)


Client side Javascript. A shopping cart application. More examples. DHTML.
Since this version of the course is Open Source I decided to stay within the original plan for the class and approach the next project right away. We will be implementing a shopping cart using JavaScript, for a small on-line bookstore.

I am further delayed posting the grades, but that's because I'd like to be more thorough and not very redundant, so here's the schedule I propose:

  1. I will be checking homework and posting grades tonight (2/27)
  2. I will be checking homework and posting grades Thursday night (3/1)
  3. I will be checking homework and posting grades Monday night (3/5)
These will be the dates when updates of Postem grades will occur. This way in the morning of the day of the midterm you will know your current situation.

Since the class is Open Source posting a complete shopping cart here is no longer a problem. The rest of the notes will be building the project. To keep things distinct, yet provide many useful references, I decided to not update my notes from years past, and just refer to them here.

  1. Here's an introduction to JavaScript with many examples.

  2. Here's a simple form-validation exercise that applies these principles.

  3. Here an overview of a more general context, notes on DHTML.

  4. Here's a pair of demos illustrating special effects with images (variation).

  5. Here's a very long document covering JavaScript.

With all this behind us, let's build the shopping cart.

We'll need this:

The rest is fairly quick (and easy).

You first need a catalog of products that you'd want to sell. The format of these files is as follows: they're (basically) plain HTML files that have a form inside that contains a hidden field whose name is "description" and whose value is a a combination of elements that describe the item and its price.

This is as far as processing goes. Of course, for the user to be able to browse the books available some text and probably a picture need to be added as well, as detailed below.

Let's say I am selling a few of my extra copies of A348 books and I have prepared the following five files:

Each one of them could be improved but for now they have the right structure and each provides a link to the next item in the catalog. I am not going to include the HTML for any of the catalog pages here since it's already available to you in your browser (and you should look at the source code before going further).

Now we need to set up a user interface for our shopping cart. Let's make it such that the user can browse the catalog in the upper part of the browser's window while the lower half of the screen offers

We use a frameset layout (frameset.html) as follows:
<html>
<head><title>Shopping Cart</title></head>

<frameset rows="60%,*">
  <frame src="item1.html" name="catalog"> 
  <frameset cols="30%,*"> 
    <frame src="control_panel.html" name="control_panel"> 
    <frame src="blank.html"         name="cart"> 
  </frameset> 
</frameset>

</html>
Try this even before you have the other two HTML files for the control panel and for the originally empty shopping cart. It should look
like this.
Notice that this entry point and the catalog files need not be in the same location, since we can specify the frame source through its URL.

Put this in your htdocs in a directory called cart.

We now create the blank.html file and then start working on the control panel (that will contain all our JavaScript code). We don't need any other file except if you have more merchandise you should add more item*.html in your catalog of items.

The blank.html file could look like this:

<html><head><title>Welcome!</title></head>
<body bgcolor=white> 
Welcome to the Shopping Cart application! <p>
 
Please browse through our on-line catalog and choose those
items of interest to you by using the control panel on the left. <p> 
 
We hope you have a useful and profitable shopping experience. 
</body>
</html>
But that, of course, is by and large irrelevant.

Let's start on the control panel: it should contain three buttons, to add or remove the current item to the shopping cart and/or to place an order. We plan to implement the "add item" functionality in these lab notes together with the order submission, and we'll leave it to you to implement the "remove item" function and to complete the order processing routine.

So the control_panel.html file could start by looking as follows:

<html><head><title>Shopping Cart Controls </title></head>

<body bgcolor=white>
<form name="form1">
<center>
<input type="button" name="add"    value="Add Item"> 
<input type="button" name="delete" value="Remove Item"> 
<input type="button" name="order"  value="Place Order"> 
</center>
</form> 
</body>
</html>
This is where our JavaScript code will go and to start with we only have the three buttons. In this lab we are going to work on the "Add Item" and "Place Order" buttons so might as well start with "Add Item" right away. The main data structure used by the control panel code is a "shopping cart" object that is called cart. The shopping cart is defined by the following constructor:
function cart() {
  this.cart           = new array(); 
  this.entries        = new array(); 
  this.add            = add; 
  this.remove         = remove; 
  this.list           = list; 
  this.show           = show; 
  this.order          = order;
  this.make_orderForm = make_orderForm; 
  return this; 
} 
Of course, to understand this function we need to clarify the meaning of seven other functions: array, add, remove, list, show, order, and make_orderForm. Since we are not set to understand everything at once we will keep the signatures of the functions as in the text and will provide empty definitions for some of them for the time being.

We need to make a global initialization, to create a new cart object when the page is loaded and we do it with the assignment

theCart = new cart(); 
in the JavaScript code that augments the control_panel.html file.

And for the code to be interpreted properly we need to provide definitions for all the functions involved, so we add those in between the <SCRIPT> and </SCRIPT> tags.

function array() { }

function add(item) { }

function remove(item) { }

function list() { }

function show(aDoc) { }

function order() { }

function make_orderForm() { }
We'll add code to them at the right time. Let's now add an event handler to the "Add Item" button because otherwise all JavaScript is of no good. We'll issue two commands when the "Add Item" button is pressed:
theCart.add(getCurrentItem());
theCart.show(parent.cart.document)
Essentially this means: So let's add a definition for getCurrentItem.

We want to keep things simple so to start with we have:

function getCurrentItem() {
  alert('A-ha! getCurrentItem has been called!'); 
  return null; 
}
And now the event handler for the "Add Item" button.

<form name="form1">
<center>
<input type="button" name="add"    value="Add Item"
       onClick="theCart.add(getCurrentItem()); 
                theCart.show(parent.cart.document)"   > 
<input type="button" name="delete" value="Remove Item"> 
<input type="button" name="order"  value="Place Order"> 
</center>
</form>
The new part is in blue.

OK, so you should try this and verify that when you click the "Add Item" button the alert window pops up, started from the new getCurrentItem function.

If you don't test now, and have a mistake, it will come to haunt you later big time.

So test it now, and re-test it often.

Now we make the following changes (marked in blue) to allow add to report the event while also giving an idea of whether the item was correctly picked or not.

function add(item) {
  alert('add has been called with ' + item.description); 
}

function remove(item) { }

function list() { }

function show(aDoc) { }

function order() { }

function make_orderForm() { }

function getCurrentItem() {
  if (parent.catalog.document.forms.length == 0) 
    return null; 
  var itemDesc = parent.catalog.document.forms[0].description.value; 
  if (itemDesc == null)
    return null; 
  return new catEntry(itemDesc);  
} 

function catEntry(string) {
  var firstColon = string.indexOf(":"); 
  var lastColon = string.lastIndexOf(":"); 
  this.catNo = string.substring(0, firstColon); 
  this.description = string.substring(firstColon + 1, lastColon); 
  this.price = string.substring(lastColon + 1, string.length); 
  return this; 
} 
Test it!

We use a new type of object, catEntry, to store an item or a catalog entry (which contains a catalog number, an item description and a price).

So far so good. We therefore turn add into its final version:

function add(item) {
  if (item == null) return; 
  if (this.cart[item.catNo]) this.cart[item.catNo]++;
  else this.cart[item.catNo] = 1; 
  this.entries [item.catNo] = item; 
}
which does not invoke another function, then we add the definition for show:
function show(aDoc) { 
  aDoc.clear(); 
  aDoc.open("text/html"); 
  aDoc.writeln("<html><head><title>Current Shopping Cart");
  aDoc.writeln("</title></head><body bgcolor=white>"); 
  aDoc.writeln(this.list()); 
  aDoc.writeln("</body></html>"); 
  aDoc.close(); 
}
This in turn calls list() so we need to provide it:
function list() { 
  var totalPrice = 0.0; 
  var result = "<table border>" + 
    "<th>Cat #<th>Description<th>Quantity<th>Unit Price"; 

  var keys = new sortKeys(this.cart); 

  for (i = 1; i <= keys.length; i++) {
    var a = keys[i]; 
    var catNo = this.entries[a].catNo; 
    var description = this.entries[a].description; 

    result += "<tr><td>" + catNo + "<td>" + description + 
      "<td>" + this.cart[a] + "<td>$" + this.entries[a].price; 

    totalPrice += this.entries[a].price * this.cart[a]; 
  }

  return result + "<tr><td><td><th>Total<td>$" + 
         formatAsPrice(totalPrice) + "</table>";   
}
And that, in turn, invokes sortKeys which is defined below:
function sortKeys (object) {
  this.length = 0; 
  for (var a in object) {
    var pos = 1; 
    while (pos <= this.length) {
      if (this[pos] > a) 
        break;
      pos++; 
    } 
    for (var i = this.length; i >= pos; i--) 
      this[i+1] = this[i]; 
    this[pos] = a; 
    this.length++; 
  } 
  return this; 
}
It also invokes formatAsPrice that needs to be defined (minimally):
function formatAsPrice (price) {
  return price; 
}
At this point you should have a working shopping cart.

Now let's place the order. This requires two things:

So we do those two things (minimally)
function order() { 
  alert('A-ha! You are trying to place an order.'); 
}

<form name="form1">
<center>
<input type="button" name="add"    value="Add Item"
       onClick="theCart.add(getCurrentItem()); 
                theCart.show(parent.cart.document)"   > 
<input type="button" name="delete" value="Remove Item"> 
<input type="button" name="order"  value="Place Order"
       onClick="theCart.order()"> 
</center>
</form> 
and test it!.

And once it tests fine we replace order with its real source code:

function order() { 
  var orderWin = window.open(""); 
  var a = orderWin.document;
  a.clear(); 
  a.open("text/html"); 
  a.writeln("<html><head><title>Order Form</title>" + 
    "</head><body bgcolor=white><h1>Order Form</h1>"); 
  a.writeln(this.make_orderForm()); 
  a.writeln("</body></html>"); 
  a.close();   
}
And we give make_orderForm its final version:
function make_orderForm() { 
  var result = "Please confirm this order list. Change the quantity " +
    "ordered to 0 to cancel an item. Press \"Order\" to submit the order." + 
    '<form action="/cgi-bin/shcart" method=POST>' + "<table BORDER>" + 
    "<th>Cat #<th>Description<th>Quantity"; 
  var keys = new sortKeys(this.cart); 
  for (i = 1; i <= keys.length; i++) {
    var a = keys[i]; 
    var catNo = this.entries[a].catNo; 
    var quantity = '<input type="text" name="item:' + 
      catNo + '" value="' + this.cart[a] + '" size=2>'; 
    result += "<tr><td>" + catNo + "<td>" + 
      this.entries[a].description + "<td>" + quantity + "\n"; 
  }
  result += "</table><p>" + 
    '<table><tr><th>Your name<td><input type="text" name="name">' + 
    '<tr><th>Customer Number<td><input type="text" name="custNo">' +
    '<tr><th>PO Number<td><input type="text" name="PO"></table><p>' + 
    '<strong>Shipping address:</string><br><textarea name="address" ' + 
    'rows = 4 cols=40></textarea><p><input type="submit" value="' + 
    'Place Order">'; 
  return result; 
}
and test it!

Then we write a shcart script that we place in cgi-bin:

#!/usr/bin/perl
 
use CGI;
 
$query = new CGI;
 
print $query->header, 
      $query->start_html(-bgcolor=>'white'); 
print "Placing an order: needs to be finished.<p><hr><p>"; 
if ($query->request_method() eq 'POST') {
  print $query->Dump,
} else {
  print "Sorry this script can process only POST requests."; 
}       
$query->end_html;
and test that it receives (and returns) the key elements of the order. You should now finish the project by adding the rest of the JavaScript code (to implement a remove method) and also making the CGI script that receives the order send a mail message to the account that should be in charge of all purchases (presumably yours).

Later (after the spring break) we will show you how you could add this to a relational database and maintain it (initialize, update, query) properly.


Last updated on Feb 27, 2001, by Adrian German for A348/A548