NoteFor general information on the JavaScript language and on using JavaScript to create client applications for Netscape Navigator, see the JavaScript Guide.
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.
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
With JavaScript, you can do both of these tasks easily.
<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
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:
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:
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.
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.
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:
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>