Полезная информация


Special Edition Using HTML 4

Previous chapterNext chapterContents


- 36 -
Developing With LiveWire Pro

by Michael Morgan and Mark R. Brown

Netscape's LiveWire Pro

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.

The "Pro" in LiveWire Pro

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.

How LiveWire Works

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.

A Brief HTTP Tutorial

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.

What Options Does Netscape Offer?

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.

What Does LiveWire Do with a Request?

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.

Understanding SQL

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.


The Relational Model

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:

Table 36.1 umerous Blanks Characterize Tables That are Not in Fourth Normal Form

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 SQL Primer

A typical life cycle of a database proceeds like this:

1. The database is created with the SQL CREATE DATABASE command:
CREATE DATABASE bookWholesale
2. Tables are created with the CREATE TABLE command:
CREATE TABLE books
(isbn char(10) not null,
title char(20) not null,
publicationYear datetime null,
retailPrice money null))
3. One or more indexes is created:
CREATE INDEX booksByYear ON books (publicationYear)
4. Data is inserted into the tables:
INSERT INTO books VALUES (`0789708019', `Webmasters Expert Solutions', 1996, 69.95)
5. Queries are run against the database:
SELECT title, publicationYear WHERE retailPrice < 40.00
For most applications, queries are the principal reason for the existence of the application.

6. Data may be changed:
UPDATE books 
SET retailPrice = 59.95
WHERE ISBN='0789708019'
7. Data may be deleted from the tables:
DELETE FROM books
WHERE publicationYear < 1990
8. The tables and even the database itself may be deleted when you no longer have a need for them.
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.

Understanding Transactions

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:

1. Suppose you call the airline and ask for a ticket to Honolulu. The ticket agent queries the database, looking for available seats, and finds one on tonight's flight. It's the last available seat. You take a minute to decide whether you want to leave so soon.

2. While you are thinking, another customer calls the airline, asks the same question, and gets the same answer. Now two customers have been offered the same seat.

3. You make your decision--you'll fly tonight. Your ticket agent updates the database to reflect the fact that the last seat has been sold.

4. The other customer now decides to take the seat. That ticket agent updates the database, selling the ticket to the other customer. The record showing that the seat was sold to you is overwritten and lost.

5. You arrive at the airport and find that no one has ever heard of you. The other customer is flying in your seat.

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:

1. You call the airline and ask for a ticket to Honolulu. The ticket agent gets a read lock and queries the database, looking for available seats. One is available on tonight's flight. It's the last available seat. You take a minute to decide whether you want to leave so soon.

2. While you are thinking, another customer calls the airline, asks the same question, and gets the same answer. Now two customers have been offered the same seat.

3. You make your decision--you'll fly tonight. Your ticket agent gets an exclusive write lock on the data, rereads the database to verify that the seat is still available, updates the database, and releases the lock.

4. The other customer now decides to take the seat. That ticket agent gets a write lock and reruns the query. The database reports that the seat is no longer available. The ticket agent informs the customer, and they work to find a different seat for that customer.

5. You arrive at the airport and take your seat on the airplane. Aloha.

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.

Understanding Cursors

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.

Introduction to Crystal Reports

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 Database Connectivity Library

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.


Opening and Closing the Connection

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.

Inserting Data into Tables

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

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);

Accessing Data a Row at a Time

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);

Accessing Data as a Set

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.

Using BLObs

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");

Transactions in LiveWire Pro

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();

Error Handling

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.

JavaScript and the Second-Generation Netscape Servers

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.

Understanding Java and JavaScript

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.

Server-Side JavaScript

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.

Table 36.2  The Programmer Uses Command-line Options to Issue Broad Directives to the 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.

The Virtual Java Machine

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.

Putting It All Together--a Database Example

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.

Listing 36.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 36.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 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.

Listing 36.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 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.

Listing 36.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>

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.

Listing 36.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 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.

Listing 36.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. Listing 36.7 shows the code for this page.

Listing 36.7  Remove.htm-- Remove the title from the database.

<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.

Listing 36.8  Sales.htm--Enable the store 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>

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.

Listing 36.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>

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.

Listing 36.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>


Previous chapterNext chapterContents


© Copyright, Macmillan Computer Publishing. All rights reserved.