Notice: This material is excerpted from Special Edition Using JavaScript, ISBN: 0-7897-0789-6. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.
One of the underutilized areas of JavaScript is client-side integration with CGI and server-side databases. With JavaScript's capability to validate information and keep a running total of items and prices, it can serve as a powerful tool to reduce the dependence on CGI scripts in generating catalog pages and order forms for users, thereby reducing the load on the server and the user's time spent on waiting for client/server communication.
Although the Books Galore! is simple in structure and application, the fundamentals used illustrate what can be done, even with a simple CGI operation on a text-based data set (see fig. 22.1).
Fig. 22.1 The opening page of Books Galore! is a simple form that enables the user to choose which types of books to browse. The results of the form are used to parse a database and retrieve the product information.c
The Books Galore! ordering demo is found at http://weber.u.washington.edu/~davidnf/java/bookstore.html. As you try out the system, don't worry about the bill: it's only a demo, so no one is waiting on the other end of the line to take your money.
David Nagy-Farkas is a senior in computer engineering at the University of Washington in Seattle. With exposure to a wide variety of computer languages over the course of his studies, including C, C++, and SmallTalk, it was only natural that he trained his sights on two new entries in the programming fray: Java and JavaScript.
"I heard about them when they were released, and started checking them out," he said. After consulting online documentation from Netscape and other sources, he started to spend some of his spare time looking at how other people were using them, and generating applets and scripts of his own. Nagy-Farkas now spends a portion of his time creating HTML, Java, JavaScript, and CGI scripts on a freelance basis.
"Initially, I was just looking at what could be done," Nagy-Farkas said. He looked at a lot of other Web sites to see what other developers were doing with JavaScript. "What I found really wasn't that practical. There were a lot of calculators and stuff like that," but nothing that really indicated some of the more powerful client-side functions that are useful when dealing with data and a server.
David Nagy-Farkas's home page for Live Web Designs is found at http://weber.u.washington.edu/~davidnf/java/bookstore.html. The page includes examples of frames and Java applets.
The product ordering system began as a simple project to keep a running total of items as a user selected or deselected items. "It was really pretty simple," Nagy-Farkas said. "I just worked with 'on-event' handlers. Anytime any field was changed that could affect the price, I recalculated the total."
Because he was still working with relatively undocumented beta releases from Netscape, his progress was hindered by a lack of information. "The running-total program was about as far as it could go with the state of the online documentation."
After more documentation was available, along with information about JavaScript's capability to write HTML, the rest of the project fell into place using arrays to keep track of items inserted into the page using the CGI script (see fig. 22.2). "All that I needed was a section of code to add the item to the array. The JavaScript functionality really made it pretty easy."
Fig. 22.2 This page is used for choosing which books to order and how many. Note the form fields at the bottom of the screen that contain the running totals. These are updated as soon as one of the fields above it is changed or clicked.
Because the project was for his own curiosity, Nagy-Farkas took his time to learn how JavaScript worked as he assembled each piece. The total time-including weeding out bugs that would pop up as each Netscape beta progressed-was about two weeks. "Wrapping it up in a book order" lasted three days, including developing the CGI scripts in Perl.
The first version of the running total program wasn't very modular. When the idea evolved to turn it into an ordering system, Nagy-Farkas realized it would be a much simpler project if the JavaScript code was converted into a series of functions for each task.
As each section was converted into a JavaScript function, it was thoroughly tested. Nagy-Farkas learned his lessons about debugging JavaScript early. "Netscape's error messages aren't that helpful," he said, citing vague system responses when a piece of code fails compounded by a line numbering system that defies explanation.
"I only write a few lines at a time and then test them to make sure they work," he said. "When something goes wrong, it's a lot easier to track down where and what it is." The other result is robust code that the programmer knows works "every step of the way."
Listing 22.1 shows a sample section of code generated by the CGI script from the introductory page. The first section, including all of the JavaScript functions, is a standard header that never changes, regardless of which book categories are chosen by the user.
Information from the database is inserted into JavaScript templates containing HTML formatting, which is also written to the document and finally to the screen by utilizing document.write functions. The only items in the document that are considered "true" HTML content are the page title and directions. By creating a generic template for the products, it is a simple matter for the CGI script to parse the database and add the items to the page.
Listing 22.1 An HTML Page with JavaScript Generated by a CGI Script. <!-- Copyright 1996 David Nagy-Farkas. All Rights Reserved --> <!-- David Nagy-Farkas reserves all rights regarding this --> <!-- code and any derived works. You may not reproduce --> <!-- this code without the explicit permission of the author. --> <HTML> <HEAD> <BODY BACKGROUND="../chalk.jpg"></BODY> <SCRIPT LANGUAGE="JavaScript"> <!-- hide the script's contents from feeble browsers // Global Variables var ForceSub = 0; var subt = 0, addtax = 0, tot = 0; // subtotal, tax, and total var tax = 0.082; // tax rate /***************** ** Add a decimal point to a number */ function AddDecimal(number) { var withdecimal = ""; var num = "" + number; if (num.length == 0) { withdecimal += "0"; } else if (num.length == 1) { withdecimal += "0.0" + num; } else if (num.length == 2) { withdecimal += "0." + num; } else { withdecimal += num.substring(0, num.length - 2); withdecimal += "." withdecimal += num.substring(num.length - 2, num.length); } return withdecimal; } /***************** ** Creates a new array of length n */ function MakeArray(n) { for (var i = 0; i <= n; i++) this[i] = 0; this.length = n; return this; } /**************** ** Creates a new Product object */ function Product(name, price) { this.name = name; this.quantity = 1; this.price = price; this.itemtot = 0; } /**************** ** Outputs HTML for a product object */ function DisplayItem(item, number) { var result = "<TR><TD>"; result += item.name + "</TD>"; result += "<TD>$" + AddDecimal(item.price) + "</TD>"; result += "<TD><INPUT TYPE='text' SIZE=5 VALUE=1 "; result += "name='" + item.name + "_quantity' "; result += "onFocus='reset(" + number + ")' onBlur='unreset(" + number + ") [ccc] '></TD>"; result += "<TD><INPUT TYPE='checkbox' VALUE='off' "; result += "name='" + item.name + "_buy' "; result += "onClick='toggle(" + number + ")'></TD>"; result += "<INPUT TYPE='hidden' name='" + item.name + "_itemprice' VALUE=" [ccc] + AddDecimal(item.price) +"></TR>"; document.write(result); } /**************** ** Outputs HTML for the top of a table column */ function PrintHead(header) { var result = "<CENTER><TABLE BORDER=4 CELLPADDING=5 CELLSPACING=2>"; result += "<TR><TH COLSPAN=4><FONT SIZE=4 COLOR='maroon'>" + header + "</FONT></TH></TR>" result += "<TR><TH>Item:</TH><TH>Cost:</TH><TH>Quantity:</TH><TH>Buy?</TH></TR>"; document.write(result); } /**************** ** Ends a table column */ function PrintTail() { document.write("</TABLE></CENTER>"); } /**************** ** Executed when the quantity of a product is selected */ function reset(index) { if (document.forms[0].elements[3*index+1].value == "on") { ForceSub = 1; compute(index); ForceSub = 0; } document.forms[0].elements[3*index].value = ""; } /*************** ** Executed when done changing the quantity of a product */ function unreset(index) { if (document.forms[0].elements[3*index].value == "") document.forms[0].elements[3*index].value = inv[index].quantity; else inv[index].quantity = eval(document.forms[0].elements[3*index].value); if (document.forms[0].elements[3*index+1].value == "on") compute(index); } /*************** ** Toggles the value of a checkbox */ function toggle(index) { if (document.forms[0].elements[3*index+1].value == "off") { document.forms[0].elements[3*index+1].value = "on"; } else if (document.forms[0].elements[3*index+1].value == "on") { document.forms[0].elements[3*index+1].value = "off"; } compute(index); } /*************** ** Perform updates of totals and tax */ function compute(index) { if (document.forms[0].elements[3*index+1].value == "on" && !ForceSub) { inv[index].itemtot = eval(inv[index].itemtot) + (eval(inv[index].price) * eval(inv[index].quantity)); subt = (eval(subt) + (eval(inv[index].price) * eval(inv[index].quantity))); } else { inv[index].itemtot = eval(inv[index].itemtot) - (eval(inv[index].price) * eval(inv[index].quantity)); subt = (eval(subt) - (eval(inv[index].price) * eval(inv[index].quantity))); } addtax = Math.round(subt * tax); tot = (eval(subt) + eval(addtax)); retotal(); } /*************** ** Redisplay the totals */ function retotal() { document.forms[0].subtotal.value = AddDecimal(subt); document.forms[0].addedtax.value = AddDecimal(addtax); document.forms[0].total.value = AddDecimal(tot); } <!-- done hiding from old browsers --> </SCRIPT> </HEAD> <CENTER><H1> Browse the Store! </H1></CENTER> <HR> <DL><DT><H3>Here are the available items you requested:</H3> <DD>Browse the selection of items and select the items you want by checking the "buy" box next to your selection. You may also specify the quantity of items you wish to purchase. At any time, you can check the bottom of the page to see a running total of the items you have selected. When you are finished, please <B>click on the "Finished" button</B> at the bottom of the page. Don't worry... there is no obligation to buy at this point... this is just a demo. </DL> <HR> <SCRIPT> <!-- Hide the script from unworthy browsers inv = new MakeArray(9); inv[0] = new Product('"The Joy of Cooking" - Hardcover', 2000); inv[1] = new Product('"The Frugal Gourmet and our Immigrant Ancestors" by Jeff Smith', 795); inv[2] = new Product('"Interview with the Vampire" by Anne Rice', 599); inv[3] = new Product('"Patriot Games" by Tom Clancy', 699); inv[4] = new Product('"The Firm" by John Grisham', 499); inv[5] = new Product('"Jurassic Park" by Michael Crichton', 650); inv[6] = new Product('"Websters New Universal Unabridged Dictionary"', 2599); inv[7] = new Product('"The New American Desk Encyclopedia"', 899); inv[8] = new Product('"Websters Thesaurus"', 795); document.write("<FORM METHOD='POST' ACTION='bookinvoice.cgi'>");PrintHead('Cooking');DisplayItem(inv[0], 0);DisplayItem(inv[1], 1);PrintTail();PrintHead('Popular Fiction'); [ccc] DisplayItem(inv[2], 2);DisplayItem(inv[3], 3);DisplayItem(inv[4], 4);DisplayItem(inv[5], 5);PrintTail();PrintHead('Reference');DisplayItem(inv[6], 6);DisplayItem(inv[7], 7);DisplayItem(inv[8], 8);PrintTail();document.write("<CENTER><TABLE CELLPADDING=4 CELLSPACING=4><TR>"); document.write(" <TD>Subtotal: </TD>"); document.write(" <TD>$</TD>"); document.write(" <TD><INPUT NAME='subtotal' VALUE=0.00 onBlur='retotal()'></TD></TR><TR>"); document.write(" <TD>+ 8.2% Sales Tax: </TD>"); document.write(" <TD>$</TD>"); document.write(" <TD><INPUT NAME='addedtax' VALUE=0.00 onBlur='retotal()'></TD></TR><TR>"); document.write(" <TD>Total: </TD>"); document.write(" <TD>$</TD>"); document.write(" <TD><INPUT NAME='total' [ccc] VALUE=0.00 onBlur='retotal()'></TD></TR>"); document.write(" </TABLE><INPUT TYPE='submit' VALUE='Done Browsing: Go to Order Form'></CENTER></FORM>"); // stop hiding --> </SCRIPT> <HR> <A HREF="http://weber.u.washington.edu/~davidnf/java/bookstore.html"> <B>Head back to the starting page</A></B> </HTML>
If it was just a matter of hard wiring the JavaScript code with the product items, this would be a simple-albeit limited-project. But it is designed to work with a database that can change in content and size from day-to-day. This is where CGI becomes a necessary companion to JavaScript.
"The page is pretty standardized," Nagy-Farkas said. "The user submits a choice for book categories, and the CGI script returns with a brand new page." With the JavaScript functions in place, it becomes a matter of getting the right information into the document.
"The toughest part is parsing the database, especially if it's a large one," he said. Books Galore! is designed to take the extraneous parsing load off the server. Current CGI online ordering systems require parsing the database after each selection and then generating a new total with a new page to display it. "If you have a big list of items, redrawing is a pain."
With Books Galore!, the database is parsed once to get the information for the initial ordering screen and is not referenced again until the final invoice (see fig. 22.3). Any changes to a customer's order on the order form is handled by JavaScript.
Fig. 22.3 After the user submits an order, another CGI script generates the final invoice with information from the order screen, including a form at the bottom for entering name, address, and payment information.
One of the key items that slowed down the development process was the lack of variable typing in JavaScript. Depending on its use, a variable can be a string or a number. To make matters worse, there aren't effective methods in place to determine what a variable thinks it is at any given moment.
"It's really a nightmare keeping track of how things are typed," Nagy-Farkas said. A variable acts according to how it's being used-which affords a lot of flexibility, but also causes a lot of confusion, in passing parameters. "It takes a little time to figure out whether that integer you just passed into a function thinks it's an integer or a string."
When this snag started to become a bigger issue in application development, he also started to realize that JavaScript is more than just an extension of HTML. "You really have to have a basic feel for programming to get this to do anything," he said. Putting workable JavaScript pages together isn't "a ten-minute project ... If you have a grasp of object-oriented programming structure, it helps a lot."
When Nagy-Farkas posted the first running total calculation application, no one seemed to be too interested. After adding the CGI script and the book order wrapping, "Feedback has been very positive" in spite of the fact the basic functionality of the page hasn't changed much-it still keeps a running total based on the most current user input.
"A lot of people have been asking for a 'vanilla' version of the Books Galore! program," Nagy-Farkas said. "It's really pretty portable right now."
By building the page at an early stage in modular components, the JavaScript code is virtually independent of the database that supplies its information. The section of the project that will change from application to application is the CGI script, which must be modified for each database. With portable code and the right CGI script, Books Galore! could be used for any virtual store-front operation.
|