Fall Semester 2002


Lecture Notes Eighteen: A Javascript Shopping Cart

With all the Javascript covered thus far behind us, let's build a shopping cart.

We'll need this (basically, the document object model, or DOM):

That's the hardest part, 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.

(Please note that this format is entirely up to you and what's shown here is simply a possible solution. In any event, once we agree to a specific way of encoding, structuring data, that committment becomes extremely important).

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.

So let's say I am selling a few of my 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. Looking at the five HTML files is extremely instructive, and I provide the source code for the first two and the last of the five files in the "catalog" below.

frilled.cs.indiana.edu%ls -ld it*
-rw-r--r--   1 dgerman       455 Aug 29  2001 item1.html
-rw-r--r--   1 dgerman       451 Aug 29  2001 item2.html
-rw-r--r--   1 dgerman       475 Aug 29  2001 item3.html
-rw-r--r--   1 dgerman       459 Aug 29  2001 item4.html
-rw-r--r--   1 dgerman       437 Aug 29  2001 item5.html
frilled.cs.indiana.edu%cat item1.html
<html>
  <head><title>Item 1</title></head>
  <body bgcolor=white>

    <img src="http://www.cs.indiana.edu/classes/a348-dger/fall2000/resources/apache.gif" 
         align=left>

    This is <em>Professional Apache</em> <p> 
    The price for this item is: $32.56<p> 

    <img src="http://www.cs.indiana.edu/classes/a113-dger/left.gif"> 
    <a href="item2.html">Next</a> item. <p> 

    <form>
      <input type=hidden 
             name=description 
             value="PA34:Professional Apache:32.56">
    </form>

  </body>

</html> 
frilled.cs.indiana.edu%cat item2.html
<html>
  <head><title>Item 2</title></head>
  <body bgcolor=white>

    <img src="http://www.cs.indiana.edu/classes/a348-dger/fall2000/resources/cookbook.jpg" 
         align=left>

    This is <em>The Perl Cookbook</em> <p> 
    The price for this item is: $29.12<p> 

    <img src="http://www.cs.indiana.edu/classes/a113-dger/left.gif"> 
    <a href="item3.html">Next</a> item. <p> 

    <form>
      <input type=hidden 
             name=description 
             value="PC01:Perl Cookbook:29.12">
    </form>

  </body> 

</html> 
For the last one, below, notice that the link takes you back to the first file.

frilled.cs.indiana.edu%cat item5.html
<html>
  <head><title>Item 5</title></head>
  <body bgcolor=white>

    <img src="http://www.cs.indiana.edu/classes/a348-dger/fall2000/resources/llama.jpg" 
         align=left>

    This is <em>Learning Perl</em> <p> 
    The price for this item is: $18.95<p> 

    <img src="http://www.cs.indiana.edu/classes/a113-dger/left.gif"> 
    <a href="item1.html">Next</a> item. <p> 

    <form>
      <input type=hidden 
             name=description 
             value="LP95:Learning Perl:18.95">
    </form>

  </body>
</html>
frilled.cs.indiana.edu%
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

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). Of course, the purchases should actually be stored in a database eventually, for order processing, and next time we will look into building a similar functionality on the server-side.


Last updated: Oct 18, 2002 by Adrian German for A348/A548