Using JavaScript

avaScript is the scripting language for application development with LiveWire. It can be executed on the server or on the client. This chapter discusses JavaScript as it relates to LiveWire.

Note For general information on the JavaScript language and on using JavaScript to create client applications for Netscape Navigator, see the JavaScript Guide.

Using client and server scripts

In general, a LiveWire application can contain JavaScript that is interpreted by the server (with the LiveWire runtime interpreter) and by the client, Netscape Navigator.

In source code HTML, client-side JavaScript is delimited by the SCRIPT tag and server-side JavaScript by the SERVER tag. LiveWire compiles the source code HTML into platform-independent bytecodes, parsing and compiling server-side JavaScript statements. At run time, the Netscape server translates the bytecodes into HTML statements (possibly including client JavaScript) and sends them across the network the client. Navigator interprets client JavaScript and performs standard HTML layout.

This process is illustrated in Figure 5.1.

Client and server JavaScript

What really happens

When you run a complex LiveWire application, a variety of things occur, some on the server and some on the client. Although the end-user does not need to know the details, it is important for you, the application developer, to understand what happens "under the hood."

In an application that contains both client and server JavaScript

What to do where

There are usually a variety of ways to partition an application between client and server. Some tasks can be performed only on the client or on the server; others can be performed on either. Although there is no definitive way to know "what to do where," you can follow these general guidelines:

As a rule of thumb, use client scripts for

Using LiveWire functions

There are a number of JavaScript functions that are built-in to LiveWire. These functions are defined only for server JavaScript.

The write function

The write function displays the values of JavaScript expressions in HTML. For example, the statement

write("<P>Customer Name is:" + project.custname)
causes LiveWire to generate HTML that is sent to the client. In this case, the HTML includes a paragraph tag and some text, concatenated with the value of the custname property of the project object, which, for example, might be "Fred's software company." The client would then receive the following HTML:

<P>Customer Name is: Fred's software company.
As far as the client is concerned, this is static HTML. However, it is actually generated dynamically by LiveWire.

The writeURL function

The writeURL function generates a URL compatible with the URL encoding methods of maintaining the client object. You do not need to use this function unless the application is using either client URL encoding or short URL encoding to maintain the client object. For more information on these these techniques, see "Techniques for maintaining the client object" on page 73.

Suppose your application has a variable, nextPage, that you set to a string identifying the page to link to, based on actions by the user. If you want to dynamically generate the a hyperlink using this property named, you could do it like this

<A HREF=`nextPage`>
Assuming the value of nextPage is set to be the appropriate URL, this would work fine. However, if you are using an URL encoding method to preserve the client object, this URL would not contain the neccessary parameters to maintain client properties. To do this, you must use writeURL as follows

<A HREF=`writeURL(nextPage)`>
You do not need to use writeURL for a normal static URL, because LiveWire will automatically append any parameters neccessary to the URL. But you must use writeURL any time an application generates an URL dynamically, if the application uses an URL encoding technique to maintain the client object.

Note Even though an application is initially installed to use one technique to maintain client, it may be modified later to use an URL encoding technique. If the application generates dynamic URLs, it would not work properly. Therefore, it is good practice always to use writeURL to generate dynamic URLs.

The redirect function

The redirect function redirects the client to the specified URL. For example,

redirect("http://www.terrapin.com/lw/apps/page2.html")
sends the client to the indicated URL. The client immediately loads the indicated page. Any previous content is discarded. The client will not display any HTML or perform any Javascript statements in the page following the redirect statement.

The debug function

The debug function outputs its argument to the Trace facility. Use this function to display the value of an expression for debugging purposes. For more information, see "Debugging an application" on page 63.

The flush function

To improve performance, LiveWire buffers write output and sends it to the client in 64Kbyte blocks. The flush function sends data resulting from write functions from the internal buffer to the client. If you do not explicitly perform m a flush, LiveWire will flush data after each 64Kbytes of generated content in a document.

You can use flush to control when data is sent to the client, for example, before an operation that will create a delay, such as a database query. Also, if a database query retrieves a large number of rows, flushing the buffer after displaying a certain number of rows prevents long delays in displaying data. Don't confuse the flush function with the flush method of File.

Note Any changes to the client object should be done before flushing the buffer if the application is using a client-side technique for maintaining the client object.

The following script is an example of using flush. This code fragment iterates through a text file and emits output for each line in the file, preceded by a line number and five spaces. Then flush causes the output to be displayed to the client. For information on performing file input and output, see "Using files on the server" on page 91.

while (!In.eof()) {
	AscLine = In.readln()
	if (!In.eof())
		write(LPad(LineCount + ": ", 5), AscLine, "\n")
	LineCount++
	flush()
}

Communicating between client and server

In developing a client-server application, keep in mind the strengths and weaknesses of client and server platforms. Servers are usually (though not always) high-performance workstations with lots of processor speed and large storage capacities. Clients are often (though not always) desktop systems with less processor power and storage capacity. However, servers can become overloaded when accessed by thousands of clients, so it can be advantageous to offload processing to the client. Preprocessing data on the client can also reduce bandwidth requirements, if the client application can aggregate data.

The LiveWire object framework preserves information over time, but client JavaScript is more ephemeral. Navigator objects exist only as the user accesses a page. Also, servers can aggregate information from many clients and many applications, and can store large amounts of data in databases. It is important to keep these characteristics in mind when partitioning functionality between client and server.

In developing applications, you might want to communicate between client and server JavaScript. In particular, you might want to

Sending values from client to server

In HTML, you send values to the server using form elements such as text fields and radio buttons. When the user clicks a submit button, the Navigator submits the values entered in the form to the server for processing. The ACTION attribute of the FORM tag determines the application wo which the values are submitted. For example

<FORM NAME="myform" ACTION="http://www.myserver.com/app/page.html">
If you want to send user-entered values to a LiveWire application, you need do nothing special. Each form element corresponds to a request property. For more information on the request object, see "Request object" on page 69.

If you want to process data on the client first, you have to create a client-JavaScript function to perform processing on the form-element values and then assign the output of the client function to a form element. The element can be hidden, so it is not displayed to the user. This enables you to perform client preprocessing.

For example, say you have a client-JavaScript function named calc that performs some calculations based on the user's input. You want to pass the result of this function to your LiveWire application for further processing. You first need to define a hidden form element for the result, as follows:

<INPUT TYPE="hidden" NAME="result" SIZE=5>
Then you need to create an onClick event handler for the submit button that assigns the output of the function to the hidden element:

<INPUT TYPE="submit" VALUE="Submit" 
onClick="this.form.result.value=calc(this.form)">
The value of result will be submitted along with any other form-element values. This value will be referenced as request.result in the LiveWire application.

Sending values from server to client

A LiveWire application communicates to the client through HTML and client-based JavaScript. If you simply want to display information to the user, then there is no subtlety: you create the dynamic HTML to format the information as you want it displayed.

However, you may want to send values to client scripts directly. You can do this in a variety of ways, including

To display an HTML form with default values set in the form elements, use the INPUT tag to create the desired form element, substituting a server-side JavaScript expression for the VALUE attribute. For example, say you want to display a text element and set the default value based on the value of client.custname. You could do this with the following statement:

<INPUT TYPE="text" NAME="customerName" SIZE="30" 
VALUE=`client.custname`>
The initial value of this text field is set to the value of the LiveWire variable client.custname.

You can use a similar technique with hidden form elements if you do not want to display the value to the user. For example,

<INPUT TYPE="hidden" NAME="custID" SIZE=5 VALUE=`client.custID`>
In both cases, these values are reflected in client-side JavaScript in property values of Navigator objects. If these two elements are in a form named "entryForm," then these values are reflected into JavaScript properties document.entryForm.customerName and document.entryForm.custID, respectively. You can then perform client processing on these values in Navigator scripts, but only if the scripts occur after the definition of the form elements in the page. For more information, see the JavaScript Guide.

You can also use LiveWire to generate client-side scripts. This is the most straightforward way of sending values from the server to client JavaScript. These values can be used in subsequent statements on the client. As a simple example, you could initialize a client-side variable named budget based on the value of client.amount as follows:

<SERVER>
write("<SCRIPT>var budget = " + client.amount + "</SCRIPT>")
</SERVER>

Using cookies for client-server communication

Cookies are a mechanism used by Navigator to maintain information between requests using a file called cookie.txt (the cookie file). The contents of the cookie file is available through the client JavaScript document.cookie property. If an application is using client cookies to maintain the client object, you can use the cookie file to communicate between client and server scripts.

The advantage of using cookies for client-server communication is that it provides a uniform mechanism for passing values between client and server and enables you to maintain persistent values on the client. For more information on using cookies for maintaining the client object, see "Techniques for maintaining the client object" on page 73.

When using client cookie for client object maintenance, LiveWire adds the following entry for each property value:

NETSCAPE_LIVEWIRE.propName=propValue;
where propName is the name of the property and propValue is its value. Special characters in propValue are encoded in the cookie file, as described in

You can use the built-in JavaScript function escape to encode characters and unescape to decode them, except these functions do not handle spaces. So your application must manually encode and decode space, forward slash (/), and the at-sign (@). Your application must encode and decode these characters.

The following are examples of functions for getting and setting cookie values. These functions assume the use of some helper functions called encode and decode that perform the proper character encoding.

function getCookie(Name) {
	var search = "NETSCAPE_LIVEWIRE." + Name + "="
	var RetStr = ""
	var offset = 0
	var end    = 0
	if (document.cookie.length > 0) {
		offset = document.cookie.indexOf(search)
		if (offset != -1) {
			offset += search.length
			end = document.cookie.indexOf(";", offset)
			if (end == -1) 
				end = document.cookie.length
		RetStr = decode(document.cookie.substring(offset, end))
		}
	}
	return (RetStr);
}
function setCookie(Name, Value, Expire) {
	document.cookie = "NETSCAPE_LIVEWIRE." + Name + "=" 
	+ encode(Value)
	+ ((Expire == null) ? "" : ("; expires=" + Expire.toGMTString()))
}
These functions could be called in client JavaScript to get and set values of the client object, for example as follows

var Kill = new Date()
Kill.setDate(Kill.getDate() + 7)
var value = getCookie("answer") 
if (value == "")
	setCookie("answer", "42", Kill)
else 
	document.write("The answer is ", value)
These statements check if there is a client property called answer. If there is not, it creates it and sets its value to 42; if there is, it displays its value.

Using files on the server

LiveWire provides a File object that enables applications to write to the server's file system. This is useful for generating persistent HTML files and for storing information without using a database server. One of the main advantages of storing information in a file instead of LiveWire objects is that the information is preserved even if the server goes down.

Important: Exercise caution when using the File object. An application can write files anywhere the operating system allows. If you create an application that write or modifies files on the server, you should ensure that users cannot misuse this capability.

For security reasons, LiveWire does not provide automatic access to the file system of client machines. If needed, the user can save information directly to the client file system by making appropriate menu choices in Navigator.

Creating a file object

To create a File object, use the standard JavaScript syntax for object creation:

fileObjectName = new File("path")
where fileObjectName is the JavaScript object name by which you will refer to the file, and path is the file path, relative to the application directory (the directory in which the web file resides). The path should be in the format of the server's file system, not a URL path.

Opening and closing a file

Once you have created a File object, you must open the file with the open method to read from it or write to it. The open method has the following syntax:

result = fileObjectName.open("mode")
This method will return true if the operation is a success and false if the operation is a failure. If the file is already open, the operation will fail and the original file will remain open.

The parameter mode is a string that specifies the mode in which to open the file. Table 5.1 describes how the file is opened for each mode.

File Access Modes

Mode

Description

r

Opens the file, if it exists, as a text file for reading and returns true. If the file does not exist, returns false.

w

Opens the file as a text file for writing. Creates a new (initially empty) text file whether or not the file exists.

a

Opens the file as a text file for appending (writing at the end of the file). If the file does not already exist, creates it.

r+

Opens the file as a text file for reading and writing. Reading and writing commence at the beginning of the file. If the file exists, returns true. If the file does not exist, returns false.

w+

Opens the file as a text file for reading and writing. Creates a new (initially empty) file whether or not the file already exists.

a+

Opens the file as a text file for reading and writing. Reading and writing commence at the end of the file. If the file does not exist, creates it.

b

Append to any of these modes to open the file as a binary file rather than a text file. Only applicable on Windows operating systems.

When an application is finished with a file, it should close the file by calling the close method. If the file is not open, close will fail. This method returns true if successful, false otherwise.

You can output the name of a file simply by using the write method. For example, the following statement displays the filename:

x = new File("/path/file.txt")
write(x)

Locking files

Most applications can be accessed by many users simultaneously, but in general it is not recommended for more than one user to access the same file simultaneously, because this can lead to unexpected errors. For example, one user of an application could move the pointer to the end of the file when another user expected the pointer to be at the beginning of the file.

To prevent multiple users from accessing the same file at one time, use the locking facility of the project and server objects, as described "Locking the project object" on page 77. Use the lock method of project to ensure that no other user can access the file when one user has it locked. If more than one application will access the same file, use the lock method of server. In general, this means you should precede all file operations with lock and follow them with unlock.

Working with files

The file object has a number of methods that you can use once a file is opened:

Positioning within a file

A file object has a pointer, that indicates the current position in the file. When you open a file, the pointer is positioned at either the beginning or the end of the file, depending on the mode you used to open it. In an empty file, the beginning and end of the file are the same.

The setPosition method positions the pointer within the file, returning true if successful; false otherwise. The syntax is

fileObj.setPosition(position [,reference])
fileObj is a file object, position is an integer indicating where to position the pointer, and reference indicates the reference point for position, as follows:

The getPosition method returns the current position in the file, where the first byte in the file is byte 0. This method returns -1 if there is an error. The syntax is

fileObj.getPosition()
The eof method returns true if the pointer is at the end of the file, and false otherwise. This method will return true after the first read operation that attempts to read past the end of file.

fileObj.eof()

Reading from a file

Use these methods to read from a file: read, readln, and readByte.

The read method reads the specified number of bytes from a file and returns a string. The syntax is

fileObj.read(count)
fileObj is a file object and count is an integer specifying the number of bytes to read. If count specifies more bytes than are left in the file, then the method reads to the end of the file.

The readln method reads the next line from the file and returns the next line from the file as a string. The syntax is

fileObj.readln()
fileObj is a file object. The line-separator characters (either \r\n on Windows or just \n on Unix or Macintosh) are not included in the string. The character \r is skipped; \n determines the actual end of the line. This compromise gets reasonable behavior on both Windows and Unix platforms.

The readByte method reads the next byte from the file and returns the numeric value of the next byte, or -1. The syntax is

fileObj.readByte()

Writing to a file

The methods for writing to a file: write, writeln, and writeByte also include a flush method to flush internal buffers to disk.

The write method writes a string to the file. It returns true if successful; otherwise it returns false. The syntax is

fileObj.write(string)
where fileObj is a file object and string is a JavaScript string.

The writeln method writes a string to the file, followed by \n (\r\n in text mode on Windows). It returns true if the write was successful; otherwise it returns false. The syntax is

fileObj.writeln(string)
The writeByte method writes a byte to the file. It returns true if successful; otherwise it returns false. The syntax is

fileObj.writeByte(number)
where fileObj is a file object and number is a number.

When you use any of the file methods for writing to a file, they are buffered internally. The flush method writes the buffer to the file on disk. This method returns true if successful, and false otherwise. If fileObj is a file object, the syntax is

fileObj.flush()

Converting data

There are two primary file formats: ASCII text and binary. The file object has two methods for converting data between these two formats: byteToString and stringToByte.

The byteToString method converts a number into a one-character string. This method is static, so no object is required. The syntax is

File.byteToString(number)
If the argument is not a number, the method will return the empty string.

The stringToByte method converts the first character of its argument, a string, into a number. This method is static, so no object is required. The syntax is

File.stringToByte(string)
The method returns a numeric value of the first character, or zero.

Getting file information

There are several file methods you can use to get information on files and to work with the error status.

The getLength method returns the number of bytes (or characters for a text file) in the file, or -1 if there is an error. The syntax is

fileObj.getLength()
The exists method returns true if the file exists; otherwise it returns false.

fileObj.exists()
The error method returns the error status or -1 if the file is not open or cannot be opened. The error status will be nonzero if an error occurred, zero otherwise (no error). Error status codes are platform dependent; refer to your operating system documentation. The syntax is

fileObj.error()
The clearError method clears both the error status (the value of error) and the value of eof:

fileObj.clearError()

Using file I/O: an example

The following simple example creates a file object, opens it for reading, and generates HTML that echoes the lines in the file, with a hard line break after each line.

x = new File("/tmp/names.txt") // path name is platform dependent
fileIsOpen = x.open("r")
if (fileIsOpen) {
	write("file name: " + x + "<BR>")
	while (!x.eof()) {
		line = x.readln()
	if (!x.eof())
	write(line+"<br>")
	}
	if (x.error() != 0)
		write("error reading file" + "<BR>")
	x.close()
}
More examples TBD.

Using external libraries

A LiveWire application can call functions written in languages such as C, C++, or Pascal and compiled into libraries on the server. Such functions are called external functions. Libraries are DLLs (dynamic link libraries) on Windows operating systems and SOs (shared objects) on Unix operating systems.

Using external functions in an application is useful if

To use an external library in a LiveWire application:

  1. Write and compile the library (DLL or SO) in a form compatible with LiveWire.
  2. With Application Manager, identify the library to be used by installing a new application or modifying installation parameters for an existing application. Once you identify an external library using Application Manager, all applications on the server can call external functions in that library.
  3. Restart the server to load the library with your application. The functions in the external library are now available to all applications on the server.
  4. In your application, use the JavaScript function RegisterCFunction to identify the functions in the library to be called, and callC to call the functions.
  5. Recompile and restart your application to make all the changes take effect.
Important: You must restart your server to install a library to use with an application. You must restart the server any time you add new library files or change the names of the library files used by an application.

Guidelines for writing C functions

Although you can write external libraries in any language, the calling conventions that LiveWire uses are C calling conventions.

Your C code must include the header file lwccall.h. Functions to be called from JavaScript must be exported, and must conform to this typdef:

typedef void (*LivewireUserCFunction)
	( int argc, struct LivewireCCallData argv[], 
	struct LivewireCCallData *result);
The header file lwccall.h is provided in \livewire\samples\ccallapp. This directory also includes the source code for a sample application that calls a C function defined in lwccall.c. Refer to these files for more specific guidelines on writing C functions for use with LiveWire.

Identifying library files

Before you can run an application that uses external libraries, you must identify the library files using Application Manager. You can identify libraries when you initially install an application (by clicking Add) or when you modify an application's installation parameters (by clicking Modify). For more information on identifying library files with Application Manager, see "Installing a new application" on page 22.

Important: After you enter the paths of library files in Application Manager, you must restart your server for the changes to take effect. You must then be sure to compile and restart your application.
Once you have identified an external library using Application Manager, all applications running on the server can call functions in the library (by using registerCFunction and callC).

Registering external functions

Use the JavaScript function registerCFunction to register an external function for use with a LiveWire application, with this syntax:

registerCFunction(JSFunctionName, libraryPath, CFunctionName)
This function returns true if it registers the function successfully. If it does not register the function successfully, it returns false. This might happen if LiveWire could not find the library at the specified location or the specified function inside the library.

The parameters are:

An application must register a function with registerCFunction before it can call it with callC. Once the application registers the function, it may call the function any number of times. A good place to register functions is in an applications initial startup page.

Using external functions in JavaScript

Once your application has registered a function with registerCFunction, it can call the function with the JavaScript function callC, using this syntax:

callC(JSFunctionName, arguments... )
This function returns a string value returned by the external function. The callC function can only return string values.

The parameters are:

Calling external functions: an example

The sample application ccallapp, installed in the LiveWire samples directory, includes C source code (in lwccall.c) that defines a C function named mystuff_EchoCCallArguments. This function accepts any number of arguments, and then returns a string that contains HTML listing the arguments. This sample illustrates calling C functions from a LiveWire application and returning values.

To run this sample application, you must compile lwccall.c with your C compiler. Command lines for several common compilers are provided in the comments in the file.

The following JavaScript statements from lwccall.html register the C function echoCCallArguments, call the function with some arguments, and then generate HTML based on the value returned by the function.

var isRegistered = registerCFunction("echoCCallArguments", 
"c:\\livewire\\samples\\ccallapp\\mystuff.dll", "mystuff_EchoCCallArguments")
if (isRegistered == true) {
	var returnValue = callC("echoCCallArguments", "first arg", 42, true, "last arg")
	write(returnValue)
}
The call to callC calls the function echoCCallArguments with the indicated arguments. The HTML generated by the write statement is

argc = 4<br>
argv[0].tag: string; value = first arg<br>
argv[1].tag: double; value = 42<br>
argv[2].tag: boolean; value = true<br>
argv[3].tag: string; value = last arg<br>