by Michael Morgan and Mark R. Brown
Netscape Communications got its start, of course, developing browsers and servers. As a company, Netscape has as much experience as anybody in using its own products to develop Web sites. In 1995, as it began to extend its line of servers, Netscape 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 older Web sites are now insufficient to sustain the growth of the Web. More and more developers were moving to the Common Gateway Interface (CGI) to enable them 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 enables Webmasters who do not have extensive programming skills to reuse components built into Java and to integrate applications with JavaScript. LiveWire Pro includes the tools that enable you to integrate a database that understands the Structured Query Language, or SQL (pronounced see-quel) into their Web sites.
Increasingly, the intranet marketplace is shaping up to a battle between Netscape Communications and Microsoft Corporation. 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 quickly produce and maintain software products. Microsoft's Windows 95 is particularly strong among corporate users and thus is commonly used on intranets. By offering its second generation servers and LiveWire on both UNIX and Windows NT, Netscape has ensured that a corporation's choice of server machine will not prevent you from choosing a Netscape solution.
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; its understanding of what works on the Net, and specifically on the Web, is its 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 (VBScript) and ActiveX Objects as its entries in this market, whereas Netscape is offering LiveWire Pro, which is bundled as a component of one of Netscape's server software packages, either Enterprise Server Pro 3.0 or SuiteSpot 3.01. The LiveWire Pro package has six components:
Using only the components of LiveWire Pro, you can develop an application that accesses and integrates data in an Informix database and serves it up as dynamic Web pages or as a data stream to a client-based application.
NOTE: More information about Crystal Reports is available online at http://www.crystalinc.com/crystalreports.
NOTE: LiveWire Pro includes support for database managers from Informix, Oracle, and Sybase, as well as support for the Microsoft open database connectivity (ODBC) standard. Through ODBC, a LiveWire 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. Compared to an ODBC-based approached, LiveWire Pro's design facilitates database configuration and provides better performance.
LiveWire Pro is available as part of Netscape's SuiteSpot package of servers. SuiteSpot consists of the following tools:
Figure 36.1 shows the SuiteSpot architecture.
FIG. 36.1
SuiteSpot insulates you from the differences between various operating systems and
hardware.
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. For more information, go to http://home.netscape.com/comprod/server_central/product/suite_spot/index.html.
Although you can build LiveWire applications without understanding how LiveWire works, such an understanding not only helps during the debugging process but also leads to a more efficient distribution of the work between the various computers available.
As you know, users access a Web site through their Web browser, for example, 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 a Uniform Resource Locator, or URL. The protocol by which requests are made and answered is the HyperText Transfer Protocol, or HTTP.
Most Webmasters also know that, in addition to offering static pages of HTML (which the browser renders into Web pages), they can write programs that run on the server. These programs follow the CGI protocol, which enables them to get information from the user and process it in ways that go well beyond the capabilities of HTTP. A CGI script typically finishes by returning some HTML to the client so the user sees a new page.
Most HTTP requests ask the server to send a specific entity (typically an HTML page) to the client. These requests contain the keyword GET. If the server is properly configured, some URLs can 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 passes the data stream coming in from the client 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. However, Netscape is offering alternatives to CGI for many reasons:
You can add CGI or Netscape's server-side alternative, LiveWire, to a corporate intranet server, just as you might add CGI or LiveWire to an Internet server. CGI scripts require special configuration of the server. The LiveWire application must be installed using the Application Manager.
TIP: Even in the relatively benign environment of an intranet, do not ignore the security concerns about CGI. Many scripts provide access to critical corporate resources and should be hardened against infiltrators from inside the company.
Netscape offers two kinds of choices to Webmasters who want to extend a site beyond the capabilities of HTTP:
Webmasters using the high-end Enterprise server can serve applications (called applets) written in Java, an object-oriented language developed specifically for the Web by Sun Micro- systems. They 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 browser understands JavaScript and can execute these programs.
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, Java applets can be compiled and run on that machine as well.
NOTE: Although client-side JavaScript and server-side JavaScript (that is, LiveWire applications) use the same language, LiveWire provides several runtime objects on the server that are crucial in building a LiveWire application. The section "Server-Side JavaScript" later in this chapter describes these objects in more detail.
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, whereas plug-ins work on just a few browsers other than Netscape Navigator. Helper applications open a separate window and run as a separate process; 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 36.2 illustrates the variety of options available to the programmer in a Netscape environment.
FIG. 36.2
If the site uses Netscape servers and the user runs Netscape Navigator, you have
many choices of where to place programs.
NOTE: JavaScript was once called "LiveScript." That name still appears in some literature, and the JavaScript compilers and interpreters still support it. Only the name has changed--the language remains the same.
Many people, particularly those who are not professional programmers, say that programming in JavaScript is easier than programming in Java. Using LiveWire, you 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, you must first understand how LiveWire handles JavaScript on the server. The LiveWire Server Extension Engine includes a script compiler for JavaScript. When a 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 that bytecodes attached to the page represent. The finished result, which includes both static HTML and dynamic program output, goes back to the client.
NOTE:etscape likes to use the term live in its literature. As Netscape uses the term, it is a synonym for dynamic as that term is used by most Webmasters. Thus live online document and dynamic Web page mean the same thing.
Recall that the single difference between LiveWire and LiveWire Pro is that LiveWire Pro provides access to relational databases. This section describes relational database management systems (RDBMSs) and their language, SQL.
Some Webmasters 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 SQL. SQL was one of the languages that emerged from early work on RDBMSs. Among RDBMS products, SQL has emerged as the clear winner. Nonrelational databases such as Object Design's object-oriented database, ObjectStore, 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 both the American National Standards Institute (ANSI) and the International Organization for Standardization (ISO) had standardized the language 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 a mechanism for creating or dropping indexes--a feature that every commercial implementation requires.
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 as 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 the relational model of data. The relational model is characterized by one or more "relations," more commonly known as tables, as illustrated in Figure 36.3. LiveWire provides direct access to tables through the Database Connectivity Library.
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.
FIG. 36.3
A single table is defined by its columns and keys; the table holds the data in rows.
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, in Figure 36.4 each book is associated with a publisher by storing the publisher's key in the book record. In the book table the publisher ID is a foreign key. In the publisher table the publisher ID is the primary key.
FIG. 36.4
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 redundancy exists between the tables, the tables may become inconsistent. For example, if the books table included the address of the publisher as well as the publisher ID, the application could update the publisher's address in the publisher table but fail to update the address in the book table.
An easy way to ensure consistency is to divide a database into many small tables, thereby avoiding redundancy. 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 that 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 they may take up too much space on the disk (because many tables will have many foreign keys). They are also likely to have poor performance because even simple queries require searches (called joins) across many tables. The best design is a trade-off 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.
A typical life cycle of a database proceeds like this:
CREATE DATABASE bookWholesale
CREATE TABLE books (isbn char(10) not null, title char(20) not null, publicationYear datetime null, retailPrice money null))
CREATE INDEX booksByYear ON books (publicationYear)
INSERT INTO books VALUES (`0789708019', `Webmasters Expert Solutions', 1996, 69.95)
SELECT title, publicationYear WHERE retailPrice < 40.00
UPDATE books SET retailPrice = 59.95 WHERE ISBN='0789708019'
DELETE FROM books WHERE publicationYear < 1990
DROP TABLE books DROP DATABASE bookWholesale
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 climb, the overhead of maintaining the indexes begins to dominate the application.
TIP: 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 multiple character types; floating-point (also known as decimal type); money; various date and time types; and even special binary types for storing sounds, images, and other large binary objects.
The Database Connectivity Library of LiveWire 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 to group several commands into a single unit of work. This unit is called a transaction. This example shows why transactions are necessary:
This sequence is a classic database problem, called the lost update. A skilled SQL programmer would solve the lost-update problem by beginning a transaction before processing the query. The database gives the ticket agent a "read lock" on the data, but the ticket agent cannot update the database with only a read lock. When the ticket agent starts to sell the seat, the application requests 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 queries the database to verify that the seat is still available. If the seat is open, the application updates the database, marking the seat as sold. Then the transaction ends, 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. Because writing to the hard drive, often over a network, is time-consuming, many database implementations store updates 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, you are more likely to say
SELECT * WHERE publicationYear = 1996
This query may return zero, one, or many records. 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, you say
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 particular cursor can update the database.
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, she or he might say
theAircraft.climb(10000);
The more common C++ technique is to have a variable hold 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 direct access memory locations opens 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.
After the cursor exists, the programmer can move it around the rows that the SELECT statement retrieved. 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. However, if you run LiveWire on a Windows NT server, you can use Crystal Reports, bundled with LiveWire Pro, to prepare ad hoc queries. Crystal Reports has five major benefits:
The latest version of Crystal Reports treats all fields, texts, and other elements as objects, which the user can place graphically on the page in the Crystal Reports Report Designer application.
The section entitled "A SQL Primer" earlier in this chapter showed the typical sequence of events in the life cycle of a database. Most Web sites that are integrated with databases enable Web users to query the database and possibly to insert or delete data. Seldom would you add or drop tables or indexes or create or delete databases.
On those occasions when the built-in 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 approach might require the programmer to write slightly different code, depending on whether the site has Informix, Oracle, Sybase, or one of the other supported databases installed.
Passthrough SQL is useful for building new databases, but it cannot 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 computationally expensive. Unlike CGI scripts, LiveWire applications remain running until you explicitly shut 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 the following databases:
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 36.5 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.
FIG. 36.5
When the system reaches steady state, you don't waste any time starting applications
or establishing database connections.
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 the login was successful:
database.connect (INFORMIX, theServer, mmorgan, mySecretWord, demoDB); if (!database.connected()) write("Error in connecting to database."); else . . .
The system stores information about the connections between applications and databases on the server in shared memory. Over time, the connection spreads to the various copies of the Netscape Server process. Figure 36.6 illustrates this mechanism, which is known as diffusion. At any time the programmer can have the application disconnect from the database--this step causes all copies of the server to disconnect from the database. You might call for a disconnect for two reasons:
FIG. 36.6
Database connections spread throughout the server until every server process is connected
to the database. Whatever the reason for calling for disconnection, it is easy
to do.
The call
database.disconnect();
disconnects all application processes from the database.
TIP: The copy of the Informix RDBMS bundled with LiveWire Pro is limited to a single connection. Although this database engine is entirely satisfactory for development, most Webmasters prefer 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 that corresponds to a discontinued book, the programmer might write
myCursor = database.cursor ("SELECT * FROM books WHERE isbn = request.discontinuedBookISBN", TRUE); myCursor.deleteRow(books);
In LiveWire Pro you can use cursors to make data available one row at a time and to get to the value stored at a row-column intersection. For example, the bookWholesale database has a table called books; the books table has a column called 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:
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 the data in a table as a list. The programmer could make a cursor and loop through all 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 36.7, with each record being hyperlinked to a more detailed single-record page such as the one in Figure 36.8. Because cursors cannot span HTML pages of an application, the best way to satisfy this requirement is to build one cursor on the list page to select all 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.
FIG. 36.7
The designer intends for the user to choose a record from this list.
In the content-oriented applications characteristic of the Web, you often want 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);
FIG. 36.8
Each selection on the list brings the user to a single-record page like this one.
ALTstring, ALIGNstring, and ISMAP are optional fields. If they are supplied, they are used in the HTML image tag. Thus the programmer of the bookWholesale 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 on 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. For example:
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. 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. Consequently, a programmer can tell the server to store information about itself and its work in a database and can then serve that information to the Net 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. It is instead compiled into hardware-independent bytecodes. Netscape implements an interpreter for these bytecodes in its products (for example, Netscape Navigator).
When the programmer completes an application (called an applet), HTML page designers can embed the applet in their pages. At runtime the applet is downloaded and executed and runs on the server.
JavaScript is an interpreted language 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 36.2 shows the five command-line options available with the LiveWire compiler.
Option | Meaning |
-c | Check only; do not generate binary file. |
-v | Verbose output; provide details during compilation. |
-d | Debug output; the resulting file output shows the generated JavaScript. |
-o | binaryFile name; give the output file this name. |
-h | 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.
You 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 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, the runtime environment creates several objects that 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 predefined 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 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 NT, the Netscape design enables you to write just one version of the program--in JavaScript. That program will run on the virtual Java 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 (in Listing 36.1) as its initial page and with home.htm (in Listing 36.2) as the default page.
<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>
<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 36.9 shows the application's home page.
FIG. 36.9
The Book Wholesalers application enables the merchant to add and delete titles, list
the inventory, and sell books.
One option given to the user is to list the titles in the database. Listing 36.3 shows how to implement this option. Figure 36.10 shows the result.
<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 36.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 36.11.
FIG. 36.10
The invent.htm page puts up a list of all books in the database.
<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>
FIG. 36.11
Addtitle.htm asks the user about the new title.
When the user submits Addtitle.htm, control passes to Add.htm (shown in Listing 36.5), which actually does the insert into the database. Control then returns to Addtitle.htm.
<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 you follow the link to Deltitle.htm, you see a list (generated from the database at runtime) of all the available titles. You click on an ISBN to remove that book from the database. Listing 36.6 shows the page, and Figure 36.12 shows what the user sees.
<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. Listing 36.7 shows the code for this page.
<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>
FIG. 36.12
The user selects a title to delete; the list is generated in server-side JavaScript
from the database.
To sell books from inventory, the store employee goes to Sales.htm. Listing 36.8 shows the code for the page, which is displayed in Figure 36.13.
<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>
FIG. 36.13
Use this page to sell books from inventory.
Listing 36.9 shows how to confirm a transaction. Figure 36.14 shows the page.
<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>
FIG. 36.14
You should confirm user-initiated changes in the database.The sold.htm page
actually does the database update. Listing 36.10 shows the code.
<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>
© Copyright, Macmillan Computer Publishing. All rights reserved.