Having explored how to control Java
from JavaScript code, we now turn to the opposite problem: how to
control JavaScript from Java code. This control is accomplished
primarily through the Java
netscape.javascript.JSObject class, which
represents a JavaScript object within a Java program. The
JavaScript-to-Java capabilities described in the previous section
typically work well in both Netscape and Internet Explorer. In
contrast, the Java-to-JavaScript techniques described here are not as
robustly supported, and you may well encounter bugs in both Netscape
and IE.
All Java interactions with JavaScript are
handled through an instance of the
netscape.javascript.JSObject class. An instance
of this class is a wrapper around a single JavaScript object. The
class defines methods that allow you to read and write property
values and array elements of the JavaScript object and to invoke
methods of the object. Here is a synopsis of this class:
public final class JSObject extends Object {
// Static method to obtain initial JSObject for applet's browser window
public static JSObject getWindow(java.applet.Applet applet);
public Object getMember(String name); // Read object property
public Object getSlot(int index); // Read array element
public void setMember(String name, Object value); // Set object property
public void setSlot(int index, Object value); // Set array element
public void removeMember(String name); // Delete property
public Object call(String methodName, Object args[]); // Invoke method
public Object eval(String s); // Evaluate string
public String toString( ); // Convert to string
protected void finalize( );
}
Because all JavaScript
objects appear in a hierarchy rooted in the current browser window,
JSObject objects must also appear in a hierarchy. To interact with
any JavaScript objects, a Java applet must first obtain a JSObject
that represents the browser window (or frame) in which the applet
appears. The JSObject class does not define a constructor method, so
we cannot simply create an appropriate JSObject.
Instead,
we must call the static getWindow(
) method. When passed a reference to an
applet, this method returns a JSObject that represents the browser
window that contains the applet. Thus, every applet that interacts
with JavaScript includes a line that looks something like this:
JSObject jsroot = JSObject.getWindow(this); // "this" is the applet itself
Having obtained a JSObject that refers to the root window of the
JavaScript object hierarchy, you can use instance methods of the
JSObject to read the values of properties of the JavaScript object
that it represents. Most of these properties have values that are
themselves JavaScript objects, so you can continue the process and
read their properties as well. The JSObject getMember( )
method returns the value of a named property, while the
getSlot( ) method returns the value of a numbered
array element of the specified JavaScript object. You might use these
methods as follows:
import netscape.javascript.JSObject; // This must be at the top of the file
...
JSObject jsroot = JSObject.getWindow(this); // self
JSObject document = (JSObject) jsroot.getMember("document"); // .document
JSObject applets = (JSObject) document.getMember("applets"); // .applets
Applet applet0 = (Applet) applets.getSlot(0); // [0]
You should note two things about this code fragment. First,
getMember( ) and getSlot( )
both return a value of type "Object", which generally
must be cast to some more specific value, such as a JSObject. Second,
the value read from slot 0 of the applets array
can be cast to an Applet, rather than a JSObject. This is because the
elements of the JavaScript applets[] array are
JavaObject
objects that represent Java Applet objects. When Java reads a
JavaScript JavaObject, it unwraps that object and returns the Java
object that it contains (in this case, an Applet). The data
conversion that occurs through the JSObject interface is documented
later in this chapter.
The JSObject class also supports
methods for setting properties and
array elements of JavaScript objects.
setMember( ) and
setSlot( ) are analogous to the
getMember( ) and getSlot( )
methods. These methods set the value of a named property or a
numbered array element to a specified value. Note, however, that the
value to be set must be a Java object. If you want to set a value of
a primitive type, use the corresponding Java wrapper class: use an
Integer object instead of an int value, for
example. Finally, the removeMember( ) method
allows you to delete the value of a named property from a JavaScript
object.
In addition to reading and writing
properties and array elements from JavaScript objects, the JSObject
class allows you to invoke methods of JavaScript objects. The
JSObject call( ) method invokes a named method of
the specified JavaScript object and passes a specified array of Java
objects as arguments to that method. As we saw when setting
JavaScript properties, it is not possible to pass primitive Java
values as arguments to a JavaScript method; instead you must use the
corresponding Java object types. For example, you might use the
call( ) method in Java code like the following to
open a new browser window:
public JSObject newwin(String url, String window_name)
{
Object[] args = { url, window_name };
JSObject win = JSObject.getWindow(this);
return (JSObject) win.call("open", args);
}
The JSObject class has one more
important method: eval( ). This Java method works
just like the JavaScript function of the same name -- it executes
a string that contains JavaScript code. You'll find that using
eval( ) is often much easier than using the
various other methods of the JSObject class. Since all the code is
passed as a string, you can use string representations of the data
types you want -- you do not have to convert Java primitive types
to their corresponding object types. For example, compare the
following two lines of code that set properties of the main browser
window:
jsroot.setMember("i", new Integer(0));
jsroot.eval("self.i = 0");
The second line is obviously easier to understand. As another
example, consider the following use of eval( ) to
write a particular frame being displayed in the browser window:
JSObject jsroot = JSObject.getWindow(this);
jsroot.eval("parent.frames[1].document.write('Hello from Java!')");
To do the equivalent without the eval( ) method is
a lot harder:
JSObject jsroot = JSObject.getWindow(this);
JSObject parent = (JSObject) jsroot.getMember("parent");
JSObject frames = (JSObject) parent.getMember("frames");
JSObject frame1 = (JSObject) frames.getSlot(1);
JSObject document = (JSObject) frame1.getMember("document");
Object[] args = { "Hello from Java!" };
document.call("write", args);
Example 22-1 shows the
init( ) method of an
applet that uses LiveConnect to interact with JavaScript.
import netscape.javascript.*
public void init( )
{
// Get the JSObject representing the applet's browser window.
JSObject win = JSObject.getWindow(this);
// Run JavaScript with eval( ). Careful with those nested quotes!
win.eval("alert('The CPUHog applet is now running on your computer. " +
"You may find that your system slows down a bit.');");
}
In order to use any applet, you must compile it and then embed it in
an HTML file. When the applet interacts with JavaScript, special
instructions are required for both of these steps.
Any applet that interacts with
JavaScript uses the netscape.javascript.JSObject
class. To compile such an applet, therefore, your Java compiler must
know where to find a definition of this class. Because the class is
defined and shipped by Netscape and not by Sun, the
javac compiler from Sun does not know about it.
This section explains how to enable your compiler to find this
required class. If you are not using the JDK from Sun, you may have
to do something a little different -- see the documentation from
the vendor of your Java compiler or Java development environment.
To tell
the JDK compiler where to find classes, you set the
CLASSPATH environment variable. This environment
variable specifies a list of directories and JAR files (or ZIP files)
that the compiler should search for class definitions (in addition to
its standard directory of system classes). The trick is to figure out
which JAR file on your system holds the definition of the
netscape.javascript.JSObject class. In Netscape
6.1, the file is plugins/java2/javaplugin.jar,
under the Netscape installation directory. In Netscape 4, the file is
java/classes/java40.jar, under the installation
directory. For Netscape 4 on a Windows system, for example, you would
probably find java40.jar at C:\Program
Files\Netscape\Communicator\Program\ Java\Classes\
java40.jar.
For Internet Explorer, the class
definition you need is usually in one of the ZIP files in
c:\Windows\ Java\Packages. The trouble is that
this directory contains a bunch of ZIP files, all of whose names are
gibberish and change from release to release! The largest of the
files is typically the one you need. You can use an unzip utility to
verify that it contains the file
netscape/javascript/JSObject.class.
Once you have found the JAR or ZIP file you need, you can tell the
compiler about it by setting the CLASSPATH
environment variable. For a Unix system, set a path like this:
setenv CLASSPATH .:/usr/local/netscape/plugins/java2/javaplugin.jar
And for a Windows system, set a path like this:
set CLASSPATH=.;C:\Windows\Java\Packages\5fpnnz7t.zip
With CLASSPATH set, you should be able to compile
your applet with javac as you would normally.
There is
an additional requirement for running
an applet that interacts with JavaScript. As a security precaution,
an applet is not allowed to use JavaScript unless the web page author
(who may not be the applet author) explicitly gives the applet
permission to do so. To give this permission, you must include the
new mayscript attribute in the applet's
<applet> tag in the HTML file.
Example 22-1 showed a fragment of an applet that used
JavaScript to display an alert dialog box. Once you have successfully
compiled this applet, you might include it in an HTML file as
follows:
<applet code="CPUHog.class" width="300" height="300" mayscript></applet>
If you do not remember to include the mayscript
attribute, the applet is not allowed to use the JSObject class.