JavaScript and the resident browser take care of most of the work here, although the user plays a small part. Consider the first page loaded—index.html. You can see what it looks like in Example 8.1.
Example 8-1. index.html
1 <HTML> 2 <HEAD> 3 <TITLE>Shopping Bag</TITLE> 4 <STYLE TYPE="text/css"> 5 <!-- 6 #welcome { text-align: center; margin-top: 150} 7 //--> 8 </STYLE> 9 <SCRIPT LANGUAGE="JavaScript"> 10 <!-- 11 var shopWin = null; 12 var positionStr = ''; 13 function whichBrowser() { 14 if(navigator.appVersion < 4) { 15 alert("You need MSIE 4.x or Netscape Navigator 4.x to use " + 16 "Shopping Bag.") 17 return false; 18 } 19 return true; 20 } 21 22 function launch() { 23 if(!whichBrowser()) { return; } 24 if(navigator.appName == "Netscape") 25 { positionStr = ",screenX=0,screenY=0"; } 26 else { positionStr = ",fullscreen=yes"; } 27 if(shopWin == null) { 28 shopWin = open("shopset.html", "", "width=" + screen.width + 29 ",height=" + screen.height + positionStr); 30 } 31 } 32 function closeUpShop() { 33 if (shopWin != null) { 34 if (typeof(shopWin) == "object") { 35 shopWin.close(); 36 } 37 } 38 } 39 window.onunload = closeUpShop; 40 //--> 41 </SCRIPT> 42 </HEAD> 43 <BODY> 44 <DIV ID="welcome"> 45 <H1>Welcome to Shopping Bag!!!</H1> 46 <A HREF="javascript: launch();">Begin</A> 47 </DIV> 48 </BODY> 49 </HTML>
That might seem like a lot of JavaScript for a page that prints only five words on the screen; however, the additional code makes for a slightly better application. JavaScript defines and initializes a top-level member for multiple window management and identifies the browser type to provide cross-platform code when the remote window opens.
The variables and function in lines 11 and 32-38 exist to enforce one rule: if the main window closes, close the remote window too. Otherwise, Shopping Bag might experience a violent JavaScript-error death if the user decides, say, to reload the remote window.
Here’s line 11:
var shopWin = null;
And lines 32-38:
function closeUpShop() { if (shopWin != null) { if (typeof(shopWin) == "object") { shopWin.close(); } } } window.onunload = closeUpShop;
Variable shopWin, originally set to null, is
later set to the remote window object (I’m jumping the gun, but
see line 27). Function closeUpShop()
is called
when this window closes. This function determines whether the user
still has the remote Shopping Bag open, and if so, closes it. If
shopWin does not equal null and is of the type
object, the remote window must be open.
closeUpShop()
closes the remote window just prior
to unloading.
The only thing that concerns the user at this point is clicking the “Begin” link to open the remote window. That opens shopset.html, a frameset. You’ll find the code in Example 8.2.
Example 8-2. shopset.html
1 <HTML> 2 <HEAD> 3 <TITLE>Shopping Bag Frameset</TITLE> 4 <SCRIPT LANGUAGE="JavaScript1.2"> 5 <!-- 6 function resetOpener() { 7 opener.shopWin = null; 8 } 9 //--> 10 </SCRIPT> 11 </HEAD> 12 <FRAMESET ROWS="80%,20%" FRAMEBORDER=0 BORDER=0 onLoad="self.focus();" 13 onUnLoad="resetOpener();"> 14 <FRAME SRC="intro.html" NORESIZE> 15 <FRAME SRC="manager.html" NORESIZE> 16 </FRAMESET> 17 </HTML>
This is your basic frameset with two rows. One is assigned a source of intro.html; the other gets manager.html. Not much JavaScript here, but let’s see what there is:
function resetOpener() { opener.shopWin = null; }
Lines 6-8 have a function named resetOpener()
called whenever the document in the parent window (in this case, the
frameset) is unloaded. By setting opener.shopWin
to null, resetOpener()
allows the user to close
the remote Shopping Bag window and reopen it again with the same
“Begin” link.
That might seem trivial, even unnecessary. Notice, however, that in
index.html (line 27), the extra window is opened
only if shopWin
equals null. Closing the window
does not set shopWin equal to null, so
resetOpener()
steps in to help. Notice also that
the onLoad event handler in the
FRAMESET
tag is set to
self.focus()
. This assures that the remote window
doesn’t open and load behind the main window, leaving the user
wondering what happened.
That basically takes care of this frameset loading. There are still three pages that still need to load—intro.html, manager.html, and inventory.js. intro.html is a static help file. As manager.html loads, the embedded source file inventory.js comes with it. manager.html is worthy of a little bit of discussion later in this section, but inventory.js has the code we need to examine now. It’s fairly long, but you’ll get a good idea of the structure used to build the inventory.
inventory.js
contains three functions. The first
two are constructor functions. One defines a product; the other
defines a product category. The last function creates arrays of
objects created by those constructors. See for yourself in Example 8.3.
Example 8-3. inventory.js
1 function product(name, description, price, unit) { 2 this.name = name; 3 this.description = description; 4 this.price = price; 5 this.unit = unit; 6 this.plu = name.substring(0, 3).toUpperCase() + 7 parseInt(price).toString(); 8 this.icon = new Image(); 9 return this; 10 } 11 function category(name, description) { 12 this.name = name; 13 this.description = description; 14 this.prodLine = eval(name); 15 var imgDir = "images/" + name.toLowerCase() + "/"; 16 for (var i = 0; i < this.prodLine.length; i++) { 17 this.prodLine[i].icon.src = imgDir + 18 this.prodLine[i].name.toLowerCase() + ".gif"; 19 } 20 return this; 21 } 22 function makeProducts() { 23 Appliances = new Array( 24 new product("Dryer", 25 "Stylish pastel design, contemporary two-button engineering.", 26 263.37 , 27 "each"), 28 new product("Hairdryer", 29 "Fancy yellowish blast, and durable cord. No expense spared.", 30 1.15, 31 "pair"), 32 new product("Oven", 33 "Made in the 1850's, this coal-powered unit quickly blackens any" + 34 "favorite dish.", 35 865.78, 36 "each"), 37 new product("Radio", 38 "Revolutionary one-channel technology. White noise and static" + 39 "included.", 40 15.43, 41 "each"), 42 new product("Toaster", 43 "BBQ-style toaster. Only a moderate shock hazard.", 44 25.78, 45 "each"), 46 new product("Washer", 47 "Does a great job on partially everything.", 48 345.61, 49 "each") 50 ); 51 52 Buildings = new Array( 53 new product("Barn", 54 "Complete with rusty silo and rotting doors. Pig sty sold" + 55 "separately.", 56 6350.57, 57 "each"), 58 new product("Lighthouse", 59 "Made of cement. Assorted light bulbs. Three AA batteries " + 60 "not included.", 61 12351.15, 62 "each"), 63 new product("Igloo", 64 "Made from top grade snow blocks, and includes a chimney and " + 65 "5-ton air conditioning unit.", 66 954.76, 67 "each"), 68 new product("City", 69 "Buildings, streets, lights, skyline. Excellent volume purchase.", 70 334165.95, 71 "each"), 72 new product("Castle", 73 "Sturdy medieval design, complete with alligators in moat, and " + 74 "remote control drawbridge.", 75 93245.59, 76 "each"), 77 new product("Tower", 78 "Really tall. Ideal for winning friends and spotting forest " + 79 "fires.", 80 24345.87, 81 "pair") 82 ); 83 84 Clothing = new Array( 85 new product("Bowtie", 86 "Swell red fabric. Doubles a bow for Christmas wreaths or " + 87 "birthday gifts.", 88 5.41, 89 "five"), 90 new product("Necktie", 91 "Be the first (and probably only) one (ever) on your block. " + 92 "Made of genuine burlap.", 93 1.15, 94 "each"), 95 new product("Purse", 96 "Attractive green material. Wards off most mammals.", 97 18.97, 98 "each"), 99 new product("Jacket", 100 "Plush fake fur with fiberglass lining. Washer safe.", 101 180.72, 102 "each"), 103 new product("Glove", 104 "Covers all four fingers and one thumb. Fancy latex design.", 105 6.59, 106 "three"), 107 new product("Dress", 108 "Found at a garage sale. Also doubles as a picnic table cover.", 109 7.99, 110 "each"), 111 new product("Watch", 112 "Geuine replica. Doesn't tell time. You have to look at it.", 113 6.19, 114 "each") 115 ); 116 117 Electronics = new Array( 118 new product("Camcorder", 119 "Solar-powered. Free microphone. Custom-built for blackmailing " + 120 "close relatives.", 121 60.45, 122 "each"), 123 new product("Stereo", 124 "Quadraphonic, pre 8-track sound. Leisure suit and roach killer " + 125 "shoes are optional", 126 54.91, 127 "each"), 128 new product("Speaker", 129 "Extra piece of hi-fi junk. Works best if discarded.", 130 1.90, 131 "each"), 132 new product("Remote", 133 "Dozens of buttons. Controls everything- TV, VCR, stereo, " + 134 "pets, local government.", 135 465.51, 136 "each"), 137 new product("Cellphone", 138 "Product of tin can technology. 35-ft calling area. Dandy " + 139 "lavender plastic.", 140 64.33, 141 "each"), 142 new product("Camera", 143 "Takes brilliant one-color photos. Landfill safe.", 144 2.95, 145 "each"), 146 new product("Television", 147 "Two-channel UHF only model. Wow.", 148 22.57, 149 "each") 150 ); 151 152 Food = new Array( 153 new product("Cheese", 154 "Wait 'til you get a wiff. Puts bleu cheese to shame.", 155 3.05, 156 "chunk"), 157 new product("Fries", 158 "More grease than the local car garage. You can't beat the " + 159 "taste, though.", 160 1.15, 161 "box"), 162 new product("Eggs", 163 "The standard breakfast staple.", 164 1.07, 165 "dozen"), 166 new product("Drumstick", 167 "This leg of pterodactyl is a sure crowd pleaser.", 168 100.00, 169 "half ton"), 170 new product("Chips", 171 "Opened-bag flavor. Guaranteed stale, or your money back.", 172 1.59, 173 "bag"), 174 new product("Shrimp", 175 "Great raw, served above room temperature.", 176 2.95, 177 "each") 178 ); 179 180 Hardware = new Array( 181 new product("Chainsaw", 182 "Be your own eager beaver with this tree-cutting machine.", 183 226.41, 184 "each"), 185 new product("Cycle", 186 "Mow down the wheat field with a few swipes. Just like the " + 187 "Grim Reaper's.", 188 11.15, 189 "each"), 190 new product("Hammer", 191 "Tempered steel head, fiberglass handle. Perfect for hitting " + 192 "things.", 193 9.87, 194 "each"), 195 new product("Lawnmower", 196 "Self-propelled (you propel it yourself).", 197 165.95, 198 "each"), 199 new product("Pliers", 200 "Perfect for eye brows and nose hairs.", 201 6.59, 202 "each"), 203 new product("Stake", 204 "This 2-in-1 miracle secures tents or gets rid of vampires.", 205 3.95, 206 "pair") 207 ); 208 209 Music = new Array( 210 new product("Bongos", 211 "Great little noise makers for even the most sophisticated " + 212 "occasions.", 213 35.50, 214 "bongo"), 215 new product("Piano", 216 "It ain't grand, but this baby will make you sound like tavern " + 217 "material in no time.", 218 1001.40, 219 "each"), 220 new product("Notes", 221 "Choose from A, B, C, D, E, F, or G. Can be reused in any song.", 222 2.97, 223 "note"), 224 new product("Guitar", 225 "Strum, strum. This one is your fast track to fame and fortune.", 226 241.11, 227 "each"), 228 new product("Trumpet", 229 "Solid copper body, and not many dents. Extra spit valve " + 230 "included.", 231 683.59, 232 "each") 233 ); 234 235 categorySet = new Array( 236 new category("Appliances", "Kitchen machines to make life easier"), 237 new category("Buildings", "Architectural structures your can't " + 238 "resist"), 239 new category("Clothing", "Fashionably questionable apparel for " + 240 "the 21st century"), 241 new category("Electronics", "Nifty gizmos that drain your wallet"), 242 new category("Food", "The best product to order over the Net"), 243 new category("Hardware", "All kinds of general purpose " + 244 "construction tools"), 245 new category("Music", "The hottest new instruments from places " + 246 "you've never heard of") 247 ); 248 }
Remember the JavaScript objects we used in the earlier chapters? They’re back with a vengeance. Each product is treated as an object with several properties; that is, each product has the following properties:
The product name
A basic description of the product
The cost of the product
The unit by which the product is sold, e.g., by the dozen, the pair, per piece
plu
The price lookup number: an arbitrary product number for inventory tracking and order processing
An image of each product
To achieve the desired result, the product constructor function is defined as follows in lines 1-10:
function product(name, description, price, unit) { this.name = name; this.description = description; this.price = price; this.unit = unit; this.plu = name.substring(0, 3).toUpperCase() + parseInt(price).toString(); this.icon = new Image(); return this; }
Notice that there are six properties created, but only four arguments expected. The number of properties and the number of expected arguments aren’t correlated, but consider how each property receives its value. The first four are obvious. Properties name, description, price , and unit are all assigned the values of the matching argument names.
plu is a different story, though. It’s
actually a composite of the name and
price properties. The uppercase of the first
three characters of name plus the integer value
of price make the PLU number. So a boat that
cost $5501.00 has the plu property of
BOA5501
. Keep in mind that this is arbitrary. The
products you sell probably have their own tracking numbers. I did it
this way to keep things simple. The last property is
icon, which for now is assigned a new
Image object. There’s no need for an
argument to do that.
We know that each product is really a product object. Likewise, each product category is really a category object. Just as products have properties, so do categories. Have a look at the properties of the category object:
The category name
A basic description of the category
All the products within that category
A category constructor saves the day in lines 11-21:
function category(name, description) { this.name = name; this.description = description; this.prodLine = eval(name); var imgDir = "images/" + name.toLowerCase() + "/"; for (var i = 0; i < this.prodLine.length; i++) { this.prodLine[i].icon.src = imgDir + this.prodLine[i].name.toLowerCase() + ".gif"; } return this; }
Each category has three properties—a string called
name, another string called
description, and an array called
prodLine. Properties name
and description seem straightforward,
but where does the array come from, and how do you get it using
eval()
? The answers to both will be clearer in a
moment, but this is the basic strategy: whatever you name the
category, the product line will be an array of the same name. For
example, if you name a category stereos, then
the array containing all the stereo products will be called
stereos. That is, prodLine
would be a copy of the variable
stereo, which is an array of different stereo
products.
Remember that each product has a property called icon, which is an Image object that hasn’t been assigned a source. Let’s get some more mileage from the category name. Not only does every category keep its product line in an array of the same name, but all the images for the products in that category are stored in a directory of the same name.
All the products in the Music category are kept in the music/ directory. The Hardware category has images in the hardware/ directory, and so on. That sounds logical. If this type of directory structure is in place, then we can preload all the images for the category when the category is constructed. Lines 16-19 handle the job:
var imgDir = "images/" + name.toLowerCase() + "/"; for (var i = 0; i < this.prodLine.length; i++) { this.prodLine[i].icon.src = imgDir + this.prodLine[i].name.toLowerCase() + ".gif"; }
If you examine the directory structure in ch08
,
you’ll see this:
images/
|
appliances/
|
buildings/
|
clothing/
|
electronics/
|
food/
|
hardware/
|
music/
|
Line 17 sets the SRC
property of each icon (an
Image) to images/ + the product name in
lowercase + / + ".gif
.” This goes
back to those naming conventions I’ve been talking about in
several previous chapters. Each product has an image of the same name
and is located in a folder with the same name as the category to
which the product belongs. Here’s the formula:
Each product image URL = images/category/product_name.gif
If you browse ch08images
, you’ll notice
each image name corresponds with a Shopping Bag product located in a
directory corresponding with a Shopping Bag category. This keeps
things simple, and lets you add, remove, and keep track of products
very easily.
If you have many large images, consider omitting image preloading. It sure is nice to have those images on the client machine so that the browsing experience has no delay. If you have lots of high-quality, large-sized images, though, the user might not be willing to wait until 500K of images load. Use your own discretion.
You’ve seen the constructor functions; now let’s put them
to work. The first thing to do is create the products, then the
categories. Function makeProducts()
does both.
Here are lines 22-248. Since much of it is the same product
constructor called repeatedly, this is the abbreviated version:
function makeProducts() { Appliances = new Array( new product("Dryer", "Stylish pastel design, contemporary two-button engineering.", 263.37 , "each"), new product("Hairdryer", "Fancy yellowish blast, and durable cord. No expense spared.", 1.15, "pair"), new product("Oven", "Made in the 1850's, this coal-powered unit quickly blackens any" + "favorite dish.", 865.78, "each"), new product("Radio", "Revolutionary one-channel technology. White noise and static" + "included.", 15.43, "each"), new product("Toaster", "BBQ-style toaster. Only a moderate shock hazard.", 25.78, "each"), new product("Washer", "Does a great job on partially everything.", 345.61, "each") ); ... ... and so on ... ... categorySet = new Array( new category("Appliances", "Kitchen machines to make life easier"), new category("Buildings", "Architectural structures you can't " + "resist"), new category("Clothing", "Fashionably questionable apparel for " + "the 21st century"), new category("Electronics", "Nifty gizmos that drain your wallet"), new category("Food", "The best product to order over the Net"), new category("Hardware", "All kinds of general purpose " + "construction tools"), new category("Music", "The hottest new instruments from places " + "you've never heard of") ); }
First come the products. Variable Appliances is set to an array. Each element in the array is a product object. Each call to product carries with it the expected arguments for building a product—a name, description, price, and unit. This happens for Buildings, Clothing, Electronics, Food, Hardware, and Music.
All that’s left is coming up with the categories. Actually, the category names are already in place (Appliances, Buildings, Clothing, etc.); we just have to let Shopping Bag know that. Lines 235-248 make it happen:
categorySet = new Array( new category("Appliances", "Kitchen machines to make life easier"), new category("Buildings", "Architectural structures your can't " + "resist"), new category("Clothing", "Fashionably questionable apparel for " + "the 21st century"), new category("Electronics", "Nifty gizmos that drain your wallet"), new category("Food", "The best product to order over the Net"), new category("Hardware", "All kinds of general purpose " + "construction tools"), new category("Music", "The hottest new instruments from places " + "you've never heard of") );
Variable categorySet is also an array. Each element in the array is a category object constructed with two arguments, a name, and a description. The first argument is assigned to the name property, and the second is assigned to the description property. Take another look at line 14 of the category constructor:
this.prodLine = eval(name);
prodLine is set to the value of
eval(name)
. So the call to
category()
in line 249 means
prodLine equals
eval("Appliances")
, which equals
Appliances. Now the category named
Appliances knows about all its products (there
in the prodLine array). Each element in
categorySet represents another Shopping Bag
category. This makes adding and removing them a snap.
The products have all been created. The only thing left to create
during load time is . . . well . . . a shopping bag. All this
shopping bag needs is a few properties to handle the payment and an
array to store all the products the user selects. The
Bag()
constructor in
manager.html defines the one and only shopping
bag. Here are lines 21-31 of manager.html, shown in Example 8.4 later in the chapter:
function Bag() { this.taxRate = .06; this.taxTotal = 0; this.shipRate = .02; this.shipTotal = 0; this.subTotal = 0; this.bagTotal = 0; this.things = new Array(); } shoppingBag = new Bag();
There are two variables to hold arbitrary rates,
taxRate
and shipRate
. One
is a multiple for computing state sales tax. The other, also a
multiple, is used to calculate shipping charges. Your tax structure
will likely differ, but you can at least see the direction here.
Three other variables,
taxTotal, subTotal
, and
shipTotal
, represent the sum of the tax, the sum
of all the product selections and their quantities, and the grand sum
that the user must pay, respectively. The last variable is an array.
things will contain all the products, including
quantities that the user selects. Variable
shoppingBag is then set to the value a new
Bag()
. Let’s shop around.
3.138.116.20