by Mike Morgan
Netscape Communications got its start, of course, developing browsers and servers. As a company, they have as much experience as anyone who uses their products to develop Web sites. In 1995, as they began to extend their line of servers, they also decided to develop application development tools. These tools are now marketed under the name LiveWire.
The very use of the term "application development," as it applies to the Web, recognizes Netscape's observation that the static HTML files of 1995 Web sites are now insufficient to sustain the growth of the Web. More and more developers were moving to CGI in order to add capabilities to their sites, but the complexity of CGI, and the talent required to develop a new script, limited the number of sites that could take advantage of this technology.
Netscape's current direction empowers those Webmasters who do not have extensive programming skills to reuse components built in Java and to integrate applications with JavaScript. LiveWire Pro includes the tools necessary to allow the Webmaster to integrate a database that understands the Structured Query Language (SQL) into the Web site.
The intranet marketplace is turning into a battle between Netscape Communications and Microsoft. Microsoft has nearly two decades of experience marketing personal computer applications. Bill Gates has succeeded in building an impressive group of analysts, programmers, and managers, who can produce software products quickly.
Netscape Communications, by contrast, was founded in 1994, and has a fraction of the resources of Microsoft. Unlike Microsoft, however, Netscape was born for the Net; their understanding of what works on the Net and, specifically, on the Web, is their greatest asset.
During the explosive growth years of personal computers, Microsoft and others made money selling interpreters for the computer language BASIC-enabling millions of people who were not professional programmers to write applications. In the battle for intranet market share, both Microsoft and Netscape understand that the winner will be the company that markets the best visual programming environment, enabling Webmasters who are not professional programmers to develop sophisticated applications for the Web.
Microsoft is promoting Visual Basic Script and ActiveX Objects as their entries in this market (as well as offering support for Java and JavaScript), whereas Netscape is offering LiveWire and LiveWire Pro. Netscape's initial LiveWire package includes four components:
| NOTE |
The Windows NT version of LiveWire also includes Crystal Reports, a sophisticated report generator from the Seagate Software Information Management Group. More information about Crystal Reports is available online at http://www.crystalinc.com/. |
LiveWire Pro includes all of the components of LiveWire. In addition,
LiveWire Pro includes the Database Connectivity Library, a set
of software that provides an Application Programming Interface
(API) between JavaScript and any of several commercial relational
databases. LiveWire Pro also includes a single-user developer
version of Informix, one of the more popular database management
systems. Using only the components of LiveWire Pro, the Webmaster
can develop an application that accesses and integrates data in
an Informix database and serves it up as dynamic Web pages or
as a datastream to a client-based application.
| NOTE |
Netscape's initial release of LiveWire Pro includes support for database managers from Informix, Oracle, and Sybase, as well as support for Microsoft's Open Database Connectivity (ODBC) standard. Through ODBC, a LiveWire Pro application can access databases built using dBASE, Visual FoxPro, and even such "standards" as text files. LiveWire Pro implements its interface to the Informix, Oracle, and Sybase libraries through the vendor's API, rather than through ODBC drivers. This design makes it easier to configure the database and gives higher performance than an ODBC-based approach. |
LiveWire Pro is available as part of Netscape's SuiteSpot tool. SuiteSpot is a collection of tools sold as one integrated package. This package consists of
The SuiteSpot architecture is illustrated in Figure 24.3.
| TIP |
Netscape's pricing is structured so that SuiteSpot costs the same as the four servers (Enterprise, Catalog, Proxy, and Mail). If you're going to buy the four servers anyway, buy SuiteSpot and get LiveWire Pro for free. |
While a Webmaster can build LiveWire applications without understanding how LiveWire works, such an understanding will help during the debugging process, and also leads to a more efficient distribution of the work between the various computers available.
All Webmasters understand that the user accesses a Web site using a Web browser such as Netscape Navigator. This software, known as the client, asks the Web server for entities such as HTML pages. The address for each such entity is known as a Uniform Resource Locator, or URL. The protocol by which requests are made and answered is the Hypertext Transfer Protocol, or HTTP.
Most Webmasters know that in addition to offering static pages of HTML, which are rendered into Web pages by the browser, they can write programs that run on the server. These programs follow the Common Gateway Interface protocol, or CGI, which enables them to get information from the user and process it in ways that go well beyond the capabilities of HTTP. Typically a CGI script will finish by returning some HTML to the client, so the user sees a new page.
Most HTTP requests ask for a specific entity (typically an HTML page) to be sent back from the server to the client. These requests contain the keyword GET. Sometimes the requested information is not a file but the output of a program. If the server is properly configured, some URLs will point to programs that are run (instead of being sent back to the client) and the output of the program is returned. Such URLs correspond to CGI scripts that are accessed using the GET method.
Other CGI scripts require more input, such as the output of an HTML form. Such scripts are written to use a different method, called POST. When the server recognizes a POST request, it starts the CGI script, and then takes the data stream coming in from the client and passes it to the "standard input" file handle (also known as STDIN) of the CGI script.
CGI is a useful general purpose mechanism-many sites use CGI successfully to e-mail the results of an HTML form to the site owner, search the site for key information requested by the user, or even query a database. So why is Netscape offering alternatives to CGI? There are many reasons.
Netscape offers two kinds of choices to the Webmaster who wants to extend the capabilities of the site beyond the abilities of HTTP: choice of the language in which the application is written, and choice of which machine the application runs on.
A Webmaster using the high-end Enterprise Web server can serve applications (called applets) written in Java, an object-oriented language developed specifically for the Web by Sun Microsystems. He or she can also write programs in JavaScript, a simplified language loosely based on Java. JavaScript is designed to be embedded in an HTML file and run on the client machine. The Netscape browsers understand JavaScript and can execute these programs.
With LiveWire or LiveWire Pro, the Webmaster can also embed JavaScript in a page and have it run on the server. Specifics on server-side JavaScript are covered later in this section.
Java applets are stored on the server but are downloaded and run on the client machine. JavaScript scripts are usually run on the client. If LiveWire is installed on the server, they can be compiled and run on that machine as well.
A programmer can also write an application for a specific platform (such as a Windows computer or a Macintosh) that integrates with the Netscape browser. These applications, called plug-ins, are activated when the server sends a specific MIME media type that the plug-in is designed to handle. Plug-ins are usually written in C++.
The predecessors of plug-ins, called helper applications, are available on all browsers, while plug-ins work on just a few browsers besides Netscape Navigator. Helper applications open a separate window and run as a separate process, while plug-ins are integrated into the client and can send messages back and forth to the Netscape browser. This tight integration allows programmers to do more with plug-ins than they can with helper applications.
Figure 24.4 illustrates the variety of options available to the programmer in a Netscape environment.
| NOTE |
JavaScript was once called "LiveScript." That name still appears in some literature, and is still supported by the JavaScript compilers and interpreters. Only the name has changed-there is only one language. |
Many people find JavaScript to be an easier language in which to program than Java-particularly so if they are not professional programmers. Using LiveWire, a Webmaster can embed JavaScript on a page but have it run on the server. Then the results of that script are sent to the client software.
To understand the role LiveWire plays, it is necessary to first understand how LiveWire handles JavaScript on the server. The LiveWire Server Extension Engine includes a script compiler for JavaScript. When the developer finishes writing a page that includes server-side JavaScript, he or she submits it to the compiler. The compiler attaches the compiled image (a set of bytecodes) to the page.
Recall that a Web server usually handles a GET request by finding
the requested entity and sending it back to the client. When LiveWire
is installed on a Netscape server, an extra step is inserted in
this process. LiveWire registers an interest in certain URLs,
and when one of those URLs is requested, the server turns control
over to the JavaScript runtime interpreter in the LiveWire Server
Extensions. That interpreter runs the code represented by the
bytecodes attached to the page. The finished result, which includes
both static HTML and dynamic program output, is sent back to the
client.
| NOTE |
Netscape likes to use the term "live" in their literature. They use the term as a synonym for "dynamic," which is used by most Webmasters. Thus, "live online document" and "dynamic Web page" mean the same thing. |
Some Webmasters, those with a background in PC applications, are
more comfortable with database managers, like dBASE, than they
are with newer programs like Visual FoxPro or Microsoft SQL Server.
Many of the newer or more powerful programs use the Structured
Query Language, or SQL (pronounced see-quel). SQL was one of the
languages that emerged from early work on relational database
management systems (RDBMSs). Among RDBMSs, SQL has proven to be
the clear winner. Non-relational databases such as ObjectStore,
Object Design's object-oriented database, often offer a SQL interface
in addition to any native Data Manipulation Language they may
support.
| NOTE |
SQL began as an IBM language, but by 1988, had been standardized by the American National Standards Institute (ANSI) and the International Organization for Standardization (ISO) as ISO-ANSI SQL. The 1988 ISO-ANSI standard described a well-defined language, but no commercial implementation exactly matched the standard. For example, the 1988 standard did not provide any mechanism for creating or dropping indexes-a facility needed in every commercial implementation. The 1989 version of the ANSI-ISO standard was more complete, but still not rich enough for commercial vendors. Netscape recommends that LiveWire Pro developers use the query format from the 1989 standard. Most commercial vendors now support the 1989 standard. The 1992 ANSI standard is much richer than the previous versions. Its page count is four times that of the 1989 standard-building a commercial implementation is a serious undertaking. To help bridge the gap, ANSI has declared the 1989 standard to be the "ANSI 92 Entry Level" standard (often called ANSI 92-compliant SQL in marketing material). The U.S. National Institute of Standards and Technology (NIST) has certified most database vendors to be compliant to the ANSI 92 Entry Level. |
Most industrial-strength database managers use what is called the relational model of data. The relational model is characterized by one or more "relations," more commonly known as tables, illustrated in Figure 24.5.
Figure 24.5 : A single table is defined by its columns and keys, and holds the data in rows.
In a well-defined database, each table represents a single concept. For example, a book wholesaler might need to model the concept of a book. Each row holds one record-information about a single title. The columns represent the fields of the record-things that the application needs to know about the book, such as the title, the publication year, and the retail price. Every table must have some combination of columns (typically just one) that uniquely identifies each row; this set of columns is called the primary key. For the book table, this column could be the book's ISBN.
Each table may also contain "pointers"-called foreign keys-to other tables by storing the primary key from the other table in its own columns. For example, each book is associated with a publisher by storing the publisher's key in the book record, as shown in Figure 24.6. In the book table, the publisher ID is a foreign key. In the publisher table, the publisher ID is the primary key.
Figure 24.6 : A foreign key links two relations.
Database design is a specialty area in computer science. If you are setting up a new database and do not have experience in database design, consider hiring a specialist to help. Relational databases are pulled in two competing directions. If there is redundancy between the tables, there is always a possibility that the tables may become inconsistent. For example, if the books table were to include the address of the publisher, as well as the publisher ID, it would be possible for the application to update the publisher's address in the publisher table but fail to update the address in the book table.
If a database is divided into many small tables, so there is no redundancy, it is easy to ensure consistency. But if the database is large, a design with many small tables may require many queries to search through tables, looking for foreign keys. Large databases with little or no redundancy can be inefficient, both in terms of space and performance.
Database designers talk about five levels of normalization-standards to ensure database consistency. The normal forms are hierarchical; a database in third normal form satisfies the guidelines for first, second, and third normal forms. Here are the guidelines which define the five normal forms:
| Author | Children | Cities Toured |
| Brady | Greg | Seattle |
| Brady | Cindy | Los Angeles |
| Brady | Bobby | |
| Clinton | Chelsea | Washington |
| Clinton | Los Angeles | |
| Clinton | St. Louis |
Databases should not be indiscriminately put into fifth normal form. Such databases are likely to have high integrity, but may take up too much space on the disk (since many tables will have many foreign keys). They are also likely to have poor performance, since even simple queries require searches (called joins) across many tables. The best design is a tradeoff between consistency and efficiency.
An empty row-column intersection is called a null. The specification of each table shows which columns are allowed to have null values.
The typical life-cycle of a database proceeds like this
| TIP |
If the number of queries is high compared to the number of inserts, deletes, and updates, indexes are likely to improve performance. As the rate at which database changes climbs, the overhead of maintaining the indexes begins to dominate the application. When a table is created, the designer specifies the data type of each column. All RDBMSs provide character and integer types. Most commercial RDBMSs also support a variety of character types, floating point (also known as decimal type), money, a variety of date and time types, and even special binary types for storing sounds, images, and other large binary objects. The Database Connectivity Library of LiveWire Pro provides mappings from a vendor-neutral set of data types, to the vendor-specific data types of the RDBMS. |
In many applications, the user needs a way of grouping several commands into a single unit of work. This unit is called a transaction. Here's an example that shows why transactions are necessary:
The above sequence is a classic database problem, called the lost update problem. A skilled SQL programmer would solve this problem by beginning a transaction before processing the query. The database would give the ticket agent a "read lock" on the data, but the ticket agent would not be able to update the database with only a read lock. When the ticket agent starts to sell the seat, the application would request an exclusive write lock. As long as that agent has the write lock, no one else can read or write that data. After the agent gets the write lock, the application would query the database to verify that the seat is still available. If the seat is open the application would update the database, marking the seat as sold. Then the transaction would end, committing the changes to the database. Here's what the lost update scenario looks like when transactions are used:
Transactions are also useful in system recovery. Since writing to the hard drive, often over a network, is time consuming, many databases are implemented so that updates are stored in local buffers for a while. If the system fails before the RDBMS can actually update the database, the system could lose some of those updates. The solution used in most commercial products is to write a record of every change to the database in a special place on the hard drive called the transaction log. If a failure occurs before the update is actually made in the database, the transaction log can be replayed during recovery to complete the update.
Webmasters, whose experience is mostly with PC-based database engines, are used to queries that return a single record. For example, dBASE III had the concept of a "pointer." The programmer could say
GOTO 3 DISPLAY
and dBASE would return all of the fields of the third record. The programmer could next enter
DISPLAY NEXT 1
and the program would advance the pointer and display record 4.
Many SQL programmers find this single-record notation a bit awkward. In SQL, one is more likely to say
SELECT * WHERE publicationYear = 1996
This query may return zero records, or one, or many. Even if the programmer "knows" that exactly one record will be returned, such as a query on the key field like
SELECT * WHERE ISBN='0789708019'
the nature of the language is such that the program still "thinks" it got back a set of records.
Many commercial SQL implementations support the concept of a cursor. A cursor is like the dBASE pointer-it indicates one record at a time, and can be moved back and forth across a set of records. LiveWire Pro supports a cursor-based construct to retrieve data. To set up a cursor the Webmaster says
myCursor = database.cursor (selectStatement, updateFlag);
where selectStatement is an ANSI 89-compliant
SQL SELECT statement, and updateFlag (which takes on
values TRUE and FALSE) controls whether the database may be updated
through this cursor.
| NOTE |
In the object-oriented language C++, an object's methods are accessed using dot notation. If the programmer has allocated a new aircraft object and wants it to climb to 10,000 feet, he or she might say theAircraft.climb(10000); It is more common in C++ to have a variable that holds the address of the aircraft object. Such a variable is called a pointer (no relation to the pointers in dBASE). To call an object's method through a pointer, the programmer uses an arrow notation, like this: theAircraftPointer->climb(10000); Pointers (in the C and C++ sense) are powerful tools, but the ability to directly access memory locations presents a security risk that the designers of Java and JavaScript were not willing to take. Unlike C++, Java and JavaScript allocate new objects, not pointers to objects, so the programmer uses the dot notation rather than the arrow notation. |
Once the cursor exists, the programmer can move it around the rows that were retrieved by the SELECT statement. For example,
myCursor.next()
loads the cursor with the next retrieved row.
Many Webmasters find the day-to-day task of building ad hoc SQL queries time-consuming, and even a bit daunting. If they run LiveWire on a Windows NT server, they can use Crystal Reports, bundled with LiveWire, to prepare ad hoc queries. Crystal Reports offers five major capabilities:
In the latest version of Crystal Reports, all fields, texts, and other elements are objects, which can be placed graphically by the user on the page in the Crystal Reports "Report Designer" application.
Earlier, this chapter (in the section titled "A SQL Primer") showed the typical steps in the life of a database. Most Web sites that are integrated with databases enable the Web user to query the database, and possibly to insert or delete data. Seldom would a Web user add or drop tables or indexes-or create or delete databases.
On those occasions when the built-in Application Programmer Interface (API) is not powerful enough to handle the application, the programmer can use passthrough SQL-a mechanism for sending any SQL to the target database. For example, the programmer could use
database.execute ("CREATE TABLE books
(isbn char(10) not null,
title char(20) not null,
publicationYear datetime null,
retailPrice money null)");
| CAUTION |
As its name implies, passthrough SQL does not attempt to interpret the SQL-it sends it straight to the target RDBMS. This fact means that the programmer may have to write slightly different code depending upon whether the site has Informix, Oracle, Sybase, or one of the other supported databases installed. Passthrough SQL is often used to build new databases. It cannot be used to bypass the cursor mechanism and return rows as a set. When retrieving data, the built-in cursor mechanism should be used rather than a native call via passthrough SQL. |
Recall that CGI scripts are started (forked) for every HTTP request. This process is com-putationally expensive. Unlike CGI scripts, LiveWire applications remain running until the Webmaster explicitly shuts them down. A side benefit to this design approach is that a LiveWire Pro application can open a connection to the database when it is started and leave that connection open almost forever.
One of the first things a LiveWire Pro application usually does when it is installed is open a connection to the database. The syntax is
database.connect(dbType, servername, username, password, databaseName);
where dbType is one of
and servername, username, password, and databaseName are the usual pieces of information needed to access a database.
Other requests to this application-whether from the same client, but for different pages, or from other clients-use the same connection to the database. Figure 24.7 shows several applications and clients interacting with databases. Not having to relaunch the application for each request improves performance on subsequent requests to the application.
An application can test its connection with the connected() method. The following code shows how to start a connection and verify that the database was found and that the logon was successful:
database.connect (INFORMIX, theServer, mmorgan, mySecretWord, demoDB);
if (!database.connected())
write("Error in connecting to database.");
else
.
.
.
Information about the connections between applications and databases is kept on the server in shared memory. Over time, the connection spreads to the various copies of the Netscape Server process, a mechanism known as diffusion. Diffusion is illustrated in Figure 24.8. At any time, the programmer can have the application disconnect from the database-this disconnect causes all copies of the server to disconnect from the database. A programmer might call for a disconnect for two reasons:
Whatever the reason for calling for disconnection, it is easy to do. The programmer calls
database.disconnect();
and all application processes disconnect from the database.
| TIP |
The copy of the Informix RDBMS bundled with LiveWire Pro is limited to a single connection. While this database engine is entirely satisfactory for development, most Webmasters will want to license a database with more connections for live use. |
All updates must be done through updatable cursors. Here's a fragment of JavaScript that makes a new updatable cursor and inserts a new row.
myCursor = database.cursor("SELECT isbn, title,
publicationYear, retailPrice FROM books", TRUE);
myCursor.isbn= "078970255x9";
myCursor.title = "Running a Perfect Netscape Site";
myCursor.publicationYear = 1996;
myCursor.retailPrice = 49.99;
myCursor.insertRow (books);
Deleting rows is easy. Start with an updatable cursor and point it to the row to be deleted. Now call the cursor's deleteRow method. For example, to delete a row which corresponds to a discontinued book, the programmer might write
myCursor = database.cursor ("SELECT * FROM books WHERE isbn =
request.discontinuedBookISBN", TRUE);
myCursor.deleteRow(books);
Data is available one row at a time in LiveWire Pro by using cursors. Cursors can be used to get to the value stored at a row-column intersection. For example, in the bookWholesale database there is a table called books that has a column retailPrice. Given a cursor that points to some row of that table, the programmer could write
thePrice = myCursor.retailPrice;
Cursors can also be set up to provide an implicit sort order, such as
myCursor = database.cursor(SELECT MAX(retailPrice) FROM books); mostExpensiveBook = myCursor[0];
The names of the columns in the SELECT list can be accessed by an index. For example, the programmer can write
myCursor = database.cursor( SELECT * FROM books); firstColumnName = myCursor.columnName(0); secondColumnName = myCursor.columnName(1);
Updatable cursors can be used to insert and delete records, or to change the fields of a record. For example, to set a new price for a book in the books table, the programmer could write
myCursor = database.cursor ("SELECT * FROM books WHERE
isbn = '0789708019',updatable);
myCursor.retailPrice = 59.95;
myCursor.updateRow(books);
Sometimes the programmer needs to show all of the data in a table as a list. The programmer could make a cursor and loop through all of the rows in the retrieved data. As a convenience, however, LiveWire Pro offers the SQLTable function.
When the programmer calls
database.SQLTable(selectStatement);
the Database Connectivity Library displays the result of the SELECT statement in an HTML table, along with column names in the header.
Often, the application design calls for a list of records like the one shown in Figure 24.9, with each record being hyperlinked to a more detailed single-record page, such as the one in Figure 24.10. Cursors cannot span HTML pages of an application, so the best way to satisfy this requirement is to build one cursor on the list page to select all of the relevant records and format each field into HTML. The single-record page would take a primary key and use it to make a new cursor, whose select statement looks up all of the fields of the record associated with that key.
Figure 24.9 : The designer intends for the user to choose a record from this list.
Figure 24.10 : Each selection on the list brings the user to a single-record page like this one.
In the content-oriented applications characteristic of the Web, the Webmaster often wants to store images, software, or audio or video clips in the database. A new database type, called the Binary Large Object (BLOb), was introduced into SQL by commercial vendors to meet these kinds of needs. For example, suppose the book wholesaler wants to store an image of the cover of the book in the database. The general syntax for retrieving an image from a BLOb and outputting it with an HTML image tag is:
myCursor.blobFieldName.blobImage (imageFormat, ALTstring, ALIGNstring, ISMAP);
ALTstring, ALIGNstring, and ISMAP are optional fields. If they are supplied they are used in the HTML image tag. Thus the programmer of the Book Wholesalers application could say:
myCursor.cover.blobImage("gif", "The cover of the book", "Left", ISMAP);
BLObs can be hyperlinked so they are read by helper applications and plug-ins, like this
blobFieldName.blobLink(mimeType, linkText);
This construct is most commonly used with large BLObs such as an audio clip. The Netscape server keeps the BLOb in memory until the user clicks another link or until a 60-second timer runs out, whichever comes first. Here's an example of how to send a BLOb to the client:
myCursor = database.cursor ("SELECT * FROM blobbedBooks");
while (myCursor.next())
{
write (myCursor.isbn);
write (myCursor.cover.blobImage("gif"));
write (myCursor.authorReading.blobLink("audio/x-wav",
"Selected highlights from" + myCursor.title);
write ("<BR>");
}
This code puts up the GIF of the book cover. When the link is selected, the client downloads and plays the audio selection-a few seconds of the author naming the highlights of the book.
BLObs are inserted into records in much the same way as other data is inserted
myCursor = database.cursor("SELECT * FROM blobbedBooks, TRUE);
myCursor.isbn="X0789708019";
myCursor.cover = blob("CoverOfWebmasters.gif");
myCursor.insertRow("blobbedBooks");
Three database methods support transaction control:
These three constructs can be used to build code like this:
database.BeginTransaction();
int db_error = 0;
dbError = database.execute ("INSERT INTO books(isbn, title)
VALUES (request.isbn, request.title);
if (!dbError)
{
dbError = database.execute ("INSERT INTO authors
VALUES (request.isbn, request.author1));
if (dbError)
database.rollbackTransaction();
else
database.commitTransaction();
}
else
// Error occurred while processing book itself
database.rollbackTransaction();
LiveWire Pro provides a degree of insulation between the programmer and the RDBMS. However, if something goes wrong, most programmers want to get the most specific error messages available-the ones generated by the RDBMS itself. To satisfy this need, the Database Connectivity Library returns two different levels of error message.
Every API call returns an error code. The programmer can test the return code-if it is false, no error occurred. TRUE returns codes that indicate the type of error (for example, server error, library error, lost connection, no memory).
If the error comes from the server or the library, the programmer can call four functions to get more specific information:
When the programmer is running the JavaScript trace utility, all error codes and messages are displayed.
Java and JavaScript play a key role in the new FastTrack and Enterprise servers, and even in the non-HTTP servers like Mail, News, Catalog, and Proxy. Each server implements a virtual Java machine and understands JavaScript. Furthermore, each server has hooks into the Database Connectivity Library. All of this means that a programmer could tell the server to store information about itself and its work in a database, and could then serve that information to the Internet via LiveWire Pro.
Java is a Web-oriented language. Like traditional languages such as C and C++, it must be compiled before the program will run. Like C++, it is object-oriented. The programmer builds objects at runtime, based on object descriptions written by the programmer, or inherited from the language's class libraries.
Unlike traditional languages, Java is not compiled into the target machine's native instruction set. Instead, it is compiled into hardware-independent bytecodes. Netscape implements an interpreter for these bytecodes in its products, like Netscape Navigator.
Once the programmer completes an application (called an applet), an HTML page designer can embed the applet in his or her page. At runtime, the applet is downloaded and executed and runs on the server.
JavaScript is an interpreted language that is loosely based on Java. JavaScript programs are stored in source form in the HTML page. At runtime, the page, with its JavaScript, is downloaded to the Netscape client and the JavaScript is interpreted and run.
If LiveWire is installed on the server, the programmer can invoke the LiveWire compiler like this
lwcomp [-cvd] -o binaryFile file
where binaryFile is the name of the output file (which typically has a file suffix of .Web) and file is the name of input file. If the input file consists of a mix of HTML and JavaScript, it has a suffix of .Html (or .Htm in a DOS/Windows environment). If the input file is pure JavaScript, it has a suffix of .Js.
Table 24.2 shows the five command-line options that are available
with the LiveWire compiler.
| Meaning | |
| Check only; do not generate binary file | |
| Verbose output; provide details during compilation | |
| Debug output; the resulting file output shows the generated JavaScript | |
| binaryFile name; give the output file this name | |
| Help; display this help message |
| TIP |
The -v (verbose) option provides so much useful information that it is almost always worth including. Get in the habit of always calling the compiler with the -v option set. |
The programmer can run the resulting binary file under the trace utility (to see each function call and its result codes). In trace, calls to the debug function in the code are activated. Some programmers will prefer to insert calls to the write function in their code to check the value of variables or verify the program logic.
When JavaScript is run under LiveWire, several objects are created by the run-time environment and are available to the programmer. The request object contains access methods to the components of the HTTP request, including members that, in CGI programming, are passed by environment variables. Examples include request.ip and request.agent. The request object also includes fields for each of a form's fields and from URLs.
The pre-defined object server contains other members that replace CGI environment variables, such as hostname, host, and port.
LiveWire uses the client object to maintain user state between requests. The application can be written to preserve user choices across requests by using Netscape cookies or other state preservation mechanisms. LiveWire offers the method client.expiration(seconds) to tell the system to destroy the client after a certain number of seconds of inactivity.
In order to provide cross-platform portability, each of the new Netscape servers includes a virtual Java machine in its architecture. Instead of writing CGI for, say, a UNIX machine, and later having to port it to Windows NT, the Netscape design allows the programmer to write just one version of the program-in JavaScript. That program will run on the Java virtual machine regardless of whether the underlying hardware and operating system is UNIX, Windows NT, or Windows 95.
This section shows a simple example application using LiveWire Pro. The application is intended to be set up with Start.htm (see Listing 24.1) as its initial page, and Home.htm (see Listing 24.2) as the default page.
Listing 24.1 Start.htm-JavaScript Connects to the Database
<HTML>
<HEAD>
<TITLE> Start Book Wholesalers Application </TITLE>
</HEAD>
<BODY>
<SERVER>
if(!database.connected())
database.connect("INFORMIX", "myserver",
"mmorgan", "ASecretWord", "booksDemo")
if (!database.connected())
write("Error: Unable to connect to database.")
else {
redirect("home.htm")
}
</SERVER>
</BODY>
</HTML>
Listing 24.2 Home.htm-A Central Point Giving the User Access to the Application's Functions
<HTML> <HEAD> <TITLE>Book Wholesalers Application</TITLE> <META name="GENERATOR" content="Mozilla/2.01Gold (Win32)"> </HEAD> <BODY> <HR> <H1>Administrative Functions</H1> <UL> <LI><A href="invent.htm">Show Inventory</A> </LI> <LI><a HREF="addTitle.htm">Add a Title</A></LI> <LI><A HREF="delTitle.htm">Delete a Title</A></LI> <LI><A HREF="sales.htm">Make a Sale </A></LI> </UL> </BODY> </HTML>
Figure 24.11 shows the application's home page.
One option given to the user is to list the titles in the database. Listing 24.3 shows how this is done. Figure 24.12 shows the result.
Figure 24.12 : The Invent.htm page puts up a list of all books in the database.
Listing 24.3 Invent.htm-Show the Active Inventory
<HTML>
<HEAD>
<TITLE> Inventory List </TITLE>
<META name="GENERATOR" content="Mozilla/2.01Gold (Win32)">
</HEAD>
<BODY>
<SERVER>
database.SQLTable("SELECT isbn,title, author,publishers.pubName,quantity
On Hand FROM books, publishers WHERE books.publisherID = publishers.publisherID");
</SERVER>
<P>
<A href="home.htm">Home</A>
</P>
</BODY>
</HTML>
The user selects the Addtitle.htm page, shown in Listing 24.4, and fills out the form to enter a new title. Note that this page builds a <SELECT> list on-the-fly from the database, as shown in Figure 24.13.
Figure 24.13 : Addtitle.htm asks the user about the new title.
Listing 24.4 Addtitle.htm-Add a New Title to the Inventory
<HTML>
<HEAD>
<TITLE> Add New Title </TITLE>
<META name="GENERATOR" content="Mozilla/2.01Gold (Win32)">
</HEAD>
<BODY>
<H1>Add a New Title</H1>
<P>Note: <B>All</B> fields are required for the new title to be accepted.
<FORM method="post" action="add.htm"></P>
<BR>Title:
<BR><INPUT type="text" name="title" size="50">
<BR>ISBN:
<BR><INPUT type="text" name="isbn" size="10">
<BR>Retail Price:
<BR><INPUT TYPE="text" name="retailPrice" size="6">
<BR>Publisher
<SELECT NAME="publisherID">
<SERVER>
publisherCursor = database.cursor("SELECT id,
name FROM publishers ORDER BY name");
while (publisherCursor.next())
{
write ("<OPTION Value="+publisherCursor.id+">"+publisherCursor.name);
}
</SERVER>
</SELECT>
<BR>
<INPUT type="submit" value="Enter">
<INPUT type="reset" value="Clear">
</FORM>
<P><A href="home.htm">Home</a> </P>
</BODY>
</HTML>
When the user submits Addtitle.htm, control passes to Add.htm (see Listing 24.5), which actually does the insert into the database. Control then returns to Addtitle.htm.
Listing 24.5 Add.htm-Complete the Process of Adding a Title
<HTML>
<HEAD>
<TITLE> Title Added </TITLE>
<META name="GENERATOR" content="Mozilla/2.01Gold (Win32)">
</HEAD>
<BODY>
<SERVER>
cursor = database.cursor("SELECT * FROM books",TRUE);
cursor.isbn = request.isbn;
cursor.title = request.title;
cursor.retailPrice = request.retailPrice;
cursor.publisherID = request.publisherID;
cursor.quantity_on_hand = 0;
cursor.updateRow(books);
redirect("addTitle.htm")
</SERVER>
</BODY>
</HTML>
When the user follows the link to Deltitle.htm, he or she sees a list (generated from the database at runtime) of all the available titles. They click an ISBN to remove that book from the database. Listing 24.6 shows the page-Figure 24.14 shows what the user sees.
Figure 24.14 : The list of books is generated by a server-side JavaScript.
Listing 24.6 Deltitle.htm-The User Prepares to Delete a Title
<HTML>
<HEAD>
<TITLE> Delete A Title</TITLE>
</HEAD>
<BODY>
<SERVER>
cursor = database.cursor("SELECT isbn, title, retailPrice,
publishers.name FROM books, publishers WHERE
books.publisherID = publishers.ID ORDER BY isbn");
</SERVER>
<TABLE border>
<CAPTION>
<CENTER><P><B><FONT SIZE=+1>Titles by ISBN</FONT></B></P></CENTER>
<CENTER><P><B><FONT SIZE=+1>Click on ISBN to remove the title</FONT>
</B></P></CENTER>
</CAPTION>
<TR>
<TH>ISBN</TH>
<TH>Title</TH>
<TH>Retail Price</TH>
<TH>Publisher</TH>
</TR>
<CAPTION>
<CENTER><P>
<SERVER>
while(cursor.next())
{
write("<TR><TD><A HREF='remove.htm?isbn='"+cursor.isbn+
"</A></TD><TD>"+cursor.title+
"</TD><TD>"+cursor.retailPrice+"</TD><TD>"+
cursor.name+"</TD></TR>");
}
</TABLE>
</BODY>
</HTML>
The Remove.htm page actually updates the database. Code for this page is shown in Listing 24.7.
Listing 24.7 Remove.htm-Actually Does the Work of Removing the Title
<HTML>
<HEAD>
<TITLE> Customer Removal </TITLE>
</HEAD>
<SERVER>
if(request.isbn != null)
{
cursor = database.cursor ("SELECT * FROM books WHERE
isbn =" + request.isbn,TRUE);
cursor.deleteRow(books)
}
redirect("delTitle.htm");
</SERVER>
</BODY>
</HTML>
To sell books from inventory, the user goes to Sales.htm. Listing 24.8 shows the code for the page, which is displayed in Figure 24.15.
Figure 24.15 : Use this page to sell books from inventory.
Listing 24.8 Sales.htm-Allows the User to Sell Books
<HTML> <HEAD> <TITLE> Sell Copies </TITLE> </HEAD> <BODY> <H1>Sell Copies</H1> <P>Note: <B>All</B> fields are required for the title to be sold. <FORM method="post" action="sell.htm"></P> <BR>ISBN: <BR><INPUT type="text" name="isbn" size="10"> <BR>Number of Copies: <BR><INPUT TYPE="text" name="copies" size="6"> <BR> <INPUT type="submit" value="Enter"> <INPUT type="reset" value="Clear"> </FORM> <P><A href="home.htm">Home</A> </P> </BODY> </HTML>
Listing 24.9 shows how to confirm a transaction.
Listing 24.9 Sell.htm-Confirm the Transaction
<HTML>
<HEAD>
<TITLE>Selling Copies</TITLE>
</HEAD>
<BODY>
cursor = database.cursor("SELECT title, isbn, retailPrice,
publishers.name, quantityOnHand FROM books, publishers
WHERE isbn=" + request.isbn +" AND
publishers.ID = books.publisherID");
if (cursor.next())
{
if (cursor.quantityOnHand > request.quantity)
{
write ("<FORM ACTION=sold.htm METHOD=GET>");
write ("<P>Confirm sale of <STRONG>" + request.copies +
</STRONG> of<BR>" + cursor.title + "<BR>ISBN " +
cursor.isbn + "<BR>Retail Price " +
cursor.retailPrice + "<BR>Publisher " +
cursor.name</P>");
write ("<INPUT TYPE=submit NAME=submit VALUE=Yes>");
write ("<INPUT TYPE=button NAME=home VALUE=No
onClick='redirect("home.htm");'>");
write ("<INPUT TYPE=hidden NAME=isbn VALUE=" +
request.isbn + ">");
write ("<INPUT TYPE=hidden NAME=quantity VALUE=" +
request.quantity + ">");
write ("</FORM>");
}
else
write ("<P>There are only " + cursor.quantityOnHand +
" copies on hand.</P>");
}
else
{
write ("<P>ISBN " + request.isbn + " not on file.</P>");
</BODY>
</HTML>
The Sold.htm page actually does the database update. Its code is shown in Listing 24.10.
Listing 24.10 Sold.htm-Complete the Sale
<HTML>
<HEAD>
<TITLE>Sold Copies</TITLE>
</HEAD>
<BODY>
<SERVER>
cursor = database.cursor("SELECT * FROM BOOKS WHERE
isbn=" + request.isbn,TRUE);
// move onto selected row
cursor.next();
cursor.quantityOnHand = cursor.quantityOnHand - request.quantity;
cursor.updateRow(books);
</SERVER>
<P>
<H1>Transaction Complete</H1>
<P>
<server>
write ("Quantity " + request.quantity + " of " + request.isbn + " sold.");
<server>
</P>
<A HREF="home.htm">Home</A>
</BODY>
</HTML>