In Navigator 3.0, Netscape introduced LiveConnect as a technology
that allows JavaScript code to interact and communicate with Java applets
and plug-ins.
In this article, I'll show how you can write pure JavaScript code that
will have
Java-like capabilities without your having to write any Java applets or
plug-ins.
I'll provide several examples of using LiveConnect that show you how to
add Java- or server-like functionality to your client-side web
applications,
essentially connecting the JavaScript and Java languages.
In this way you can extend your programming skills and improve your web
applications
by adding advanced JavaScript and Java features, such as the new
capabilities of
JavaScript 1.2 and Java's privilege-based security model.
You need to know at least the basics of the Java and JavaScript
languages to understand
what's going on in LiveConnect. I'm assuming in this article that you're
familiar with the basic syntax
of each language as well as their fundamental differences.
I also assume that you're familiar with the functionality of the
common JDK classes and methods.
BASIC SYNTAX FOR LIVECONNECT
The basic syntax for applying LiveConnect to JavaScript is not very
different from the way you write regular JavaScript code.
The LiveConnect architecture runs silently in the background as a part of
the Navigator engine,
so you don't need to know everything that's going on inside Navigator to
write code for LiveConnect.
I'm not going to discuss the architecture details of the Navigator
engine,
since it would make this article considerably longer.
If you want detailed information, there are several great
books and online resources that discuss
LiveConnect architecture
and technology in Navigator and Netscape Enterprise Server. From these
resources, you can learn
more about JavaScript JavaPackage objects, JavaClass
objects,
and JavaObject objects, including exactly how they interact with
the
standard Java system classes built into Navigator and how
LiveConnect handles the data type conversion.
The following is a very basic LiveConnect code example:
<SCRIPT>
var myPrintln = java.lang.System.out.println;
</SCRIPT>
As you can see, I simply created a JavaScript variable reference named
myPrintln
referring to the Java static class method
java.lang.System.out.println() that's built into Navigator; this
method
prints a line to the Java console.
I can now call this variable in my code as if I were actually calling
the java.lang.System.out.println() method in Navigator's
built-in Java classes.
Try the following HTML form button, which does just that:
You should see "print to the Java Console" in the Java console; to
open this console in Communicator,
choose Java Console from the Communicator menu (the one labeled
with the Communicator icon).
This is what the code for the Form button looks like:
<FORM>
In this same way, you can refer to all of the classes in the version
of the Java langauge that's built into Navigator,
because every Navigator window object contains the java,
sun,
and netscape properties,
which are references to the associated Java classes.
(Actually, they're references to the associated JavaScript
JavaPackage
objects representing the related Java classes that are represented by the
JavaClass object --
but again, I'm not going to get into these architecture details.)
As an alternative syntax, you can also call Java's println()
method more directly in LiveConnect, without
creating variable references, as in the following example:
<SCRIPT>
java.lang.System.out.println('print any string');
</SCRIPT>
You can add this type of code in-line, with a link like the following:
The equiv with special characters inserted should look like:
<A HREF=/old?u=http%3A%2F%2Fdeveloper.iplanet.com%2Fviewsource%2Fsmith_js2java%2F%26quot%3Bjavascript%3Ajava.lang.System.out.println%28&y=1999'print any string');
">java.lang.System.out.println('print any string');</A>
SAMPLE DEBUGGING TECHNIQUE
Referencing and calling println() is not only easy, it's also
a very useful
technique for debugging or tracking the state in your scripts during
development.
This technique should sound familiar to those of you who use the built-in
JavaScript
alert()
method for debugging.
For example, suppose we write a script that increments a variable every
time we call a
certain function, such as the following script:
<SCRIPT LANGUAGE="JavaScript">
Now we call the function, with this button:
We used the alert() method to print the value of the variable
foo
in an alert dialog box, and it reflects our mistake (of not first
defining foo) by
printing "foo=NaN" in the dialog.
This technique is a quick and easy way to debug a script; however,
opening and closing alert
dialogs can get pretty annoying,
especially if you're trying to debug something that calls your alert
debugger
several times in a row, such as in a for or while loop.
So, in this next example, we'll do the same thing using the
println()
method rather than alert().
<SCRIPT LANGUAGE="JavaScript">
Call the function from the following button:
The println() method prints the value of the variable
foo to the Java console.
You can click this button as many times as you want, and it will keep
printing as did the
alert() method, except you don't have to bother with closing the
alert dialog every time.
Now we'll fix the bug by properly defining the variable foo,
like so:
<SCRIPT LANGUAGE="JavaScript">
Call this function as many times as you like:
Looking in the Java console, you should see a value printed for
foo, properly incremented.
REFERRING TO JAVA AWT CLASSES
Now we'll take LiveConnect a bit further by illustrating how you can
refer to other
Java classes that are built into Navigator:
specifically, how you can refer to Java AWT classes with LiveConnect.
We'll connect JavaScript to Java by creating a new instance of a Java
class using the JavaScript new keyword.
Example 1 shows a function named browse() that opens a native
"file dialog" using the built-in
java.awt.Frame and java.awt.FileDialog classes.
This example was actually taken from the code that I wrote inside
Visual DHTML for saving files.
Example 1
<SCRIPT LANGUAGE="JavaScript" ID="Ex1">
The following button calls the browse() function:
The function opens the file dialog and then,
after a file is selected, calls the alert() method
to display the selected file name and path.
As you can see from the example JavaScript code,
I created a variable named Frame that invokes an instance of
the java.awt.Frame class using the new keyword,
and a variable named fd (for "file dialog") that invokes an
instance of
java.awt.FileDialog.
If you're familiar with these common Java classes, this code should be a
piece of cake
to understand.
And, with the flexibility of the JavaScript and Java languages, you
should be able to rewrite this code
in various ways for different web applications.
ACCESSING USER PREFERENCES WITH SIGNED OBJECTS
This section introduces some of the new power and flexibility of
LiveConnect
in JavaScript 1.2 inside Netscape Navigator 4.0 or Communicator.
Communicator brings LiveConnect to a whole new level, by introducing a
policy for JavaScript security
based on the new Java security model,
Object Signing.
To make use of the new policy in JavaScript, you must use the Java
security classes and
then digitally sign your JavaScript scripts.
For more information on signed scripts, see
JavaScript Security in Communicator 4.x.
Example 2 shows how to read "user preferences" --
something many JavaScript developers have long wanted
to do but couldn't because of the previous security restrictions.
Reading or writing user preferences was purposely not allowed in
Navigator for protection
and privacy reasons.
Now it is allowed, with the new interactive security model in
Communicator:
users are prompted with the Java Security dialog, which provides the
option to either
grant or deny the requested privilege at run time.
Example 2
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex2">
The first line of code in the readPref() function calls the
public static
enablePrivilege() method of the Java security
PrivilegeManager class, which
is contained in the netscape.security package.
When the enablePrivilege() method is called, the digital
signature is verified
and, if the signature is valid, the Java Security dialog box opens.
If the signature is not valid, the script will not be executed and an
alert dialog will
display a warning message that an uncaught
ForbiddenTargetException Java exception
error occurred.
If you want to trap that exception in JavaScript, see the section on error handling.
This example shows how you generally request expanded privileges from
the new Java security model.
In this case I passed the UniversalPreferencesRead parameter,
which
is the system target that's required for reading user preferences.
You must grant the requested privilege for Navigator to continue
executing the script.
Now, call the readPref() function from the following button:
If you grant this requested privilege, the function will display an
alert dialog
containing the value of the prefName parameter. In this case the
value of prefName
is the value of the mail.identity.useremail property, which
refers to the user's e-mail address as set by the user.
In the prefs.js file in your Communicator user profile
directory, you'll find
this user preference property, along with all the other user preference
(user_prefs)
properties, listed according to how you set them.
(Your user profile directory is usually
C:\Program Files\Netscape\Communicator\Users\your name\,
depending on
your system and where you installed Communicator.)
Note that I've added
ID attributes
to the script and button tags, because signing inline and event handler
JavaScript scripts requires ID attributes for generating inline
signatures.
You not only can read the user preferences that are kept in
Communicator's prefs.js file,
you also can read Java system properties, as illustrated in Example 3.
Example 3
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex3">
You can call the readProp() function from the following
button:
Notice how similar the readProp() function is to the
readPref() function
in Example 2.
This time we passed a different system target parameter:
UniversalPropertyRead
instead of UniversalPreferencesRead.
Because we're requesting a different system target, the PrivilegeManager
will open another
Java Security dialog box for this function;
to avoid this, you should use macro targets whenever possible.
For more information on security privilege targets, see
Netscape System Targets.
If you're concerned about signing your scripts, note that Communicator
can recognize codebases as principals.
You can enable codebase principal support in your client by adding the
following user
preference to your prefs.js file:
This next example also illustrates accessing Java system properties:
specifically, those related to
reading and writing files. We'll look at how to write JavaScript code
using LiveConnect to read
and write file data by accessing
the File class in Java's I/O library.
This technique can be very useful in web applications for storing and
retrieving user-specific
information,
such as saving the state between user sessions and processes or saving
user preferences.
Example 4 is based on a function used in Visual DHTML for saving
files.
It shows a JavaScript function, writeFile(), that uses
LiveConnect to create a
stream in order to write to a file, using an instance of Java's
DataOutputStream class.
Example 4
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex4">
In the first four lines of this function, I chose to find and write
the file in the user.home directory, getting the value from the
Java system property
as in the previous section. This way I stay in my user profile directory.
I also used the file.separator Java system property to make this
script work better
across platforms, since different platforms use different file separators.
The rest of the code in writeFile() requires a different
privilege: that of the
UniversalFileAccess target, for permission to create the stream
for writing to the file.
In LiveConnect, this can be done by setting a JavaScript variable to
refer to an instance of
java.io.DataOutputStream.
You can call writeFile() with this button:
Look in the Java console for feedback, and also in your user profile
directory,
where you'll find a new file named myFile.txt that was created
by the
writeFile() button and function.
I've defined writeFile() to have two parameters -- the file name
and an
arbitrary string to be written out --
just to illustrate that you can create a generic function for writing
different things to different files, based on the function parameters,
for a variety of web
applications.
Example 5 shows how to read the same file we just wrote to.
(If you know the Java language really well and are getting the hang of
this LiveConnect stuff, you might want
to skip to the next section, since this is very much like the
writeFile() function.)
Note that you will probably want to do more error handling here in order to catch potential
I/O exceptions,
especially for certain platforms that don't let you dynamically create
files.
Example 5
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex5">
The main differences between readFile() and writeFile()
are that readFile() reads
lines of data from the file using Java's
DataInputStream class and returns the file data.
Call readFile() from the following button:
This function opens an alert dialog displaying the file data that was
read via a LiveConnect
instance of java.io.DataInputStream.
CREATING A CLIENT-SIDE DATABASE
Now that we've seen how to access files using LiveConnect,
we'll use JavaScript accessor functions to create a very simple
client-side (flat-file)
database for reading, writing, and
modifying records.
The setDatabaseRecord() function in Example 6 is similar to
the writeFile()
function in Example 4,
except that setDatabaseRecord() reads a database file,
looking for the record name. Each record is kept on a single line.
If the record is found, the function replaces that record with the value
passed as an argument;
otherwise, it appends the name and value parameters to the database as a
new record.
Example 6
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex6">
You can call the setDatabaseRecord() function from the
following links to see how this works:
After calling the function from these links, you should find these
name and
value parameters recorded in the myDatabase.txt file that was
created in your user profile directory.
You can also look in the Java console and see the records being printed,
because
I added the java.lang.System.err.println() method for debugging
purposes.
Example 7 shows the getDatabaseRecord() function, for reading
the value of an
individual record.
Example 7
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex7">
This function searches the database file looking for the
record name, similar to the setDatabaseRecord() function.
If the record is found, it gets and returns the value of the record;
otherwise,
it returns the value as null.
You can call getDatabaseRecord() from the following links:
Did you find this LiveConnect code simple, powerful, and potentially
useful?
You can do a lot more for your web applications using JavaScript-based
LiveConnect, especially for
offline usage, hit tracking, personalization, and other traditionally
server-side functionality -- all on the client side.
MAKING SOCKET CONNECTIONS
In this section we'll see how to make secure socket connections using
LiveConnect, enabling your
web applications to send client-side data to a remote destination.
You can now make the same socket connections with JavaScript that you can
with Java.
Example 8 shows how to use LiveConnect to make a JavaScript socket
connection
for sending an e-mail message to users in HTML format. Notice that
accessing sockets is an
expanded security privilege that involves requesting the
UniversalConnect target.
Example 8
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex8">
The first three lines of the sendMail() function should look
familiar if you've read
the previous sections of this article.
We find the user's e-mail address for the recipient variable by
getting
the value of the navigator.preference property, as in the earlier
user preferences example,
and we get the SMTP server address from the
network.hosts.smtp_server property.
The rest of the code requires the UniversalConnect target
privilege to create
the socket to connect to the SMTP server.
If you're familiar with sockets, you probably don't need further
explanation, especially if you know SMTP.
In a nutshell:
The smtp variable invokes an instance of
java.net.Socket
for creating the socket.
The smtpIn variable invokes an instance of
DataInputStream for
tracking the server response (not required, but useful for error handling).
The smtpOut variable invokes an instance of
java.io.PrintStream for printing
the commands to the server.
With the Java console open in Communicator,
call the sendMail() function from the following button:
I've defined sendMail() to have two parameters --
the subject and the body for the email message.
You should see the results from the SMTP server printed to the Java
console,
because I created the var println reference in the code
to print the server response after each SMTP command.
Note that Example 8 doesn't do any error
handling; feel free to improve it to suit your needs.
Example 9 shows how to write LiveConnect code that uses the
java.net.URL class
for making a socket connection to fetch the "source" of a web page.
You can do this for the purpose of dynamically parsing or reading certain
strings or values contained
in the source, or for modifying the source of the web page.
In this case, I'm simply counting the lines of the web page and printing
the line numbers
and lines of the source to the Java console. Notice that the code also
looks
for the <SCRIPT LANGUAGE='JavaScript'> tag.
Example 9
<SCRIPT LANGUAGE="JavaScript1.2" ID="Ex9">
With the Java console open, call the fetchWebpage() function
from the following button:
Notice that the script uses java.io.DataInputStream, opening
a stream
to read the lines of the web page.
At the end of the web page, when there are no more lines to read, the
script closes
the stream and uses an alert dialog to display the results of whether the
web page
contained the <SCRIPT LANGUAGE='JavaScript'> tag.
You can improve and modify this code according to your needs.
If you like, you can make more complicated socket connections that
request the
POST method or connect to various servers on different ports or
protocols, just as
in Java code, since LiveConnect can also access all of the other classes
contained in the java.net package built into Navigator.
Note, however, that you have to be careful when making socket connections
in JavaScript code,
because you don't want to write code that will expose a password or
anything else that you don't
want users to find by viewing the source code, unless you're writing it
for use within a
secure intranet.
It's best only to make connections that prompt the user for that
information whenever possible,
or make connections that don't require having confidential information
contained in the
source code.
ERROR HANDLING
As you may have noticed by now, if you click the Deny button to deny a
privilege, or if the digital signature
is not valid for a script requesting a privilege,
the script will not be executed, and an alert dialog will display a
warning message
that an uncaught ForbiddenTargetException Java exception error
occurred.
If you want to trap that exception in JavaScript, you'll need to add an
onError handler.
I've included a very simple event handler for this page called
handleErrors(),
but you'll probably want to add your own error handlers to trap specific
exceptions,
especially for environments that you're less sure about or platforms that
implement
stricter security.
You can create your own event handler for displaying messages before
the JavaScript error
dialog appears by adding the following line of code:
window.onError = myErrorHandler;
You can also directly override Navigator's built-in onError()
method, by adding your
own onError() function, like this:
<SCRIPT LANGUAGE="JavaScript1.2">
Note that Netscape plans on implementing exception handling in JavaScript
1.3 to make this
much easier.
APPLET AND PLUG-IN COMMUNICATION
Although applets and plug-ins are not the focus of this article, I'll
briefly mention that using
LiveConnect can significantly improve your existing web applications that
contain Java applets or plug-ins.
This article has discussed LiveConnect for JavaScript-to-Java
communication;
on the other side of the coin, you can do the opposite with LiveConnect
to achieve
Java-to-JavaScript communication via your Java applet code,
as well as achieve Java-to-plug-in and plug-in-to-JavaScript
communication using plug-ins.
For example, JavaScript code can access the public methods and control
your Java applet,
just as it can access other Java classes.
LiveConnect also uses the netscape.javascript.JSObject class
built into
Navigator, which you can add to your applet for Java-to-JavaScript
communication.
These kinds of possibilities allow you to provide many of the JavaScript
features
in your Java applets and in your web applications that are not exposed to
Java.
For more sources of information on applet and plug-in communication,
see the resources
section at the end of this article.
ENDLESS POSSIBILITIES
The possibilities LiveConnect offers JavaScript programmers are endless.
There are already several very innovative things being done today with
JavaScript 1.2 and
LiveConnect.
I hope the code samples in the article help you with using LiveConnect
for creating
and improving your web applications.
FURTHER RESOURCES
LiveConnect examples:
Visual DHTML, which uses live examples of LiveConnect
View Source wants your feedback! Write to us and let us know
what you think of this article.
Many thanks to Paul Dreyfus for the opportunity to write this article,
to Caroline Rose for the fantastic editing job,
and to Norris Boyd for reviewing the security issues.
Gary Smith
is a Netscape engineer, creator of
Visual DHTML,
Dialog Widget, and
Webtop Widget.
He works on new technology projects involving LiveConnect, JavaScript,
Java, CGI, XML/RDF, and Web-crawling technologies, such as Netcaster and Channel Finder. Occasionally he writes sample code
and documentation for DevEdge Online.