Use server scripts for
Generating dynamic HTML
The write function generates HTML based on the value of JavaScript expression given as its argument. For example, the statement
write("<P>Customer Name is:" + project.custname)
causes LiveWire to generate HTML including 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.
Flushing the output buffer
To improve performance, LiveWire buffers write output and sends it to the client in 64 Kbyte blocks. The flush function sends data resulting from write functions from the internal buffer to the client. If you do not explicitly perform a flush, LiveWire will flush data after each 64 Kbytes of generated content in a document. Don't confuse the flush function with the flush method of File.
You can use flush to control when data are 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 every few rows prevents long delays in displaying data.
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.
For information on
performing file input
and output, see
"Using files on the
server."
The following script shows how flush is used. 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.
while (!In.eof()) {
AscLine = In.readln()
if (!In.eof())
write(LPad(LineCount + ": ", 5), AscLine, "n")
LineCount++
flush()
}
Redirecting the client
The redirect function redirects the client to the specified URL. For example,
redirect("http://www.whatever.com/lw/apps/page2.html")
sends the client to the indicated URL. The client immediately loads the indicated page, discarding any previous content. The client will not display any HTML or perform any JavaScript statements that follow the call to redirect.
You can use redirect to dynamically generate URLs. For example, if a page defines a request variable choice, you can redirect the client to a page based on the value of choice as follows:
redirect("http://www.whatever.com/lw/apps/page"
+ request.choice ".html")
If your application is using client URL encoding or server URL encoding to maintain the client object, you must use addClient or writeURL to append client property values to generated URLs. For more information, see "Maintaining client properties with URL encoding."
Displaying debugging information
The debug function outputs its argument to the trace facility. Use this function to display the value of an expression for debugging purposes. For example, the statement
debug("Current Guess is ", request.guess)
displays the value of the guess property of the request object in the trace window or frame along with some identifying text.
For more information, see "Debugging applications."
Using select lists
The HTML SELECT tag allows multiple values to be associated with a single form element, with the MULTIPLE attribute. If your application requires select lists that allow multiple selected options, you must use the getOptionValue function to get the values in JavaScript.
The syntax of getOptionValue is
getOptionValue(name, index)
where name is specified by the NAME attribute of the SELECT tag, and index is the zero-based ordinal index of the selected option. The function returns the value of the selected item, as specified by the associated OPTION tag.
The associated function getOptionValueCount returns the number of options (specified by OPTION tags) in the select list. It requires only one argument, the name of the SELECT tag.
For example, suppose you have the following form element:
<SELECT NAME="what-to-wear" MULTIPLE SIZE=8>
<OPTION SELECTED>Jeans
<OPTION>Wool Sweater
<OPTION SELECTED>Sweatshirt
<OPTION SELECTED>Socks
<OPTION>Leather Jacket
<OPTION>Boots
<OPTION>Running Shoes
<OPTION>Cape
</SELECT>
You could process the input from this select list as follows:
var loopIndex = 0
var loopCount = getOptionValueCount("what-to-wear")
while ( loopIndex < loopCount ) {
var optionValue = getOptionValue("what-to-wear",loopIndex)
write("<br>Item #" + loopIndex + ": " + optionValue + "n")
loopIndex++
}
If the user kept the default selections, this script would return
Item #1: JeansItem #3: SweatshirtItem #4: Socks
Maintaining client properties with URL encoding
When using client URL encoding or server URL encoding to maintain the client object, client property values are maintained in the URLs requested by the client, either directly or using a generated name. LiveWire automatically appends these values to standard HTML hyperlinks to other pages. For more information on URL encoding methods of maintaining the client object, see "Techniques for maintaining the client object."
If your application only uses HTML to link between its pages, you do not have to do anything special. However, if the application generates URLs dynamically or uses the redirect function, you must use the addClient function to append client property values to URLs.
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. Therefore, if your application generates dynamic URLs, you should always use addClient.
Suppose your application has a variable, nextPage, that you set to a string identifying the page to link to, based on user actions. You could generate a hyperlink using this property like this:
<A HREF=`nextPage`>
Assuming the value of nextPage is a string representing the desired URL, this would work fine. However, if you are using a URL-encoding method to preserve the client object, this URL would not contain the client properties. To do this, you must use addClient as follows:
<A HREF=`addClient(nextPage)`>
You must also use addClient when you use the redirect function. For example,
redirect(addClient("mypage.html"))
Encoding values in URLs
When generating a URL request string, certain characters are special characters, including ampersand (&), plus sign (+), equal sign (=), question mark (?), and blank ( ), among others. These characters must be encoded by escape sequences when used in URLs.
The escape and unescape functions are provided for encoding in a URL values that may include special characters. In general, if an application needs to generate its own name/value pairs in an URL request, you should use escape and unescape, to ensure that all values are interpreted properly.
The escape function returns the ASCII value in hexadecimal for a character, preceded by a percent sign (%), with the exception of a blank (space) character; it encodes this as a plus sign (+). For example, escape("a bc")
returns the string "%61+%62%63".
Here is an example of using escape in server-side JavaScript:
<A HREF=\Q"mypage.html?val1=" + escape("the value")\Q)>Click Here</A>
This example allows the string "the value" to be assigned to the request property val1; if you did not use escape, this would be an illegal URL because it includes a space (blank) character.
The LiveWire implementation of escape and unescape are similar to their implementation in client JavaScript, except for the treatment of spaces. For more information see the JavaScript Guide.
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 fast processors 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 easily accomplish both tasks.
Sending values from client to server
In HTML, you use form elements such as text fields and radio buttons to send values to the server. 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 to 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 "The
request object."
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
<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>
cookie.txt
(the cookie file). The contents of the cookie file are 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 they provide a uniform mechanism for passing values between client and server and make it possible to maintain persistent values on the client.
For information on
using cookies for
maintaining the client
object, see "Techniques for maintaining
the client object."
When you use client cookies to maintain the client object, 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 Navigator JavaScript functions escape to encode characters and unescape to decode them. Because these functions do not handle spaces, your application must manually encode and decode spaces, as well as forward slash (/) and the at-sign (@).
The following are examples of client-side JavaScript 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, as in the following example:
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)
This group of statements checks whether there is a client property called answer. If there is not, it creates it and sets its value to forty-two; 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 in 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 read or write files anywhere the operating system allows. You should be sure your application does not allow an attacker to read password files or other sensitive information or to write files at will. For more information, see "Security considerations."
For security reasons, Navigator 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 refer to the file, and path is the complete file path. The path should be in the format of the server's file system, not a URL path.
You can display the name of a file by using the write method, with the File object as its argument. For example, the following statement displays the filename:
x = new File("/path/file.txt")
write(x)
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. This table describes how the file is opened for each mode.
When an application is finished with a file, it can 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.
Locking files
Most applications can be accessed by many users simultaneously. In general, however, it is not recommended that different users simultaneously access the same file, because unexpected errors may result. 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 a file at the same time, use the locking facility of the project and server objects, as described in "Locking the project object." Use the lock method of project to ensure that other users cannot access a file that a user has locked. In general, this means you should precede all file operations with lock and follow them with unlock. If more than one application will access the same file, use the lock method of server. If one user has the file locked, other users of the application will wait until the file becomes unlocked.
For example, suppose you have created a file called myFile
. Then you should use it as follows:
project.lock()
myFile.open("r")
// use the file as needed
myFile.close()
project.unlock()
In this way, only one user of the application has access to the file at one time.
Working with files
The File object has a number of methods that you can use once a file is opened:
fileObj.setPosition(position [,reference])where 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:
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 the file. The syntax is
fileObj.eof()
fileObj.read(count)where 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 it as a string. The syntax is
fileObj.readln()where fileObj is a File object. The line-separator characters (either rn 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()
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 (rn 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()
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.
fileObj.getLength()The exists method returns true if the file exists; otherwise it returns false. The syntax is
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()
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() }
lwccall
contains source and header files illustrating how to call functions in external native functions from a LiveWire application.
To use an external library in a LiveWire 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 livewiresampleslwccall
. 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
Note: Backslash () is a special character in JavaScript, so you must use a double backslash () to separate Windows directory and filenames in libraryPath.
callC(JSFunctionName, arguments... )This function returns a string value returned by the external function. The callC function can return only string values. The parameters are
lwccall
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 would 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:livewiresamplesccallappmystuff.dll", "mystuff_EchoCCallArguments") if (isRegistered == true) { var returnValue = callC("echoCCallArguments", "first arg", 42, true, "last arg") write(returnValue) }Then 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>