[Chapter 19] 19.6 Using JavaScript from Java
Author: David Flanagan
Date: Fri Aug 29 11:05:19 1997

#

| Chapter 19 LiveConnect: JavaScript and Java |

## 19.6 Using JavaScript from Java

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 netscape.javascript.JSObject class. Just as a JavaObject is a JavaScript wrapper around a Java object, so a JSObject is a Java wrapper around a JavaScript object.

### The JSObject Class

All Java interactions with JavaScript are performed through a single interface--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. A synopsis of this class appears in the code Example 19.4.

#### Example 19.4: Synopsis of the netscape.javascript.JSObject 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 at the current browser window, JSObjects must also appear in a hierarchy. In order for a Java applet to interact with any JavaScript objects, it 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 itself, this method returns a JSObject that represents the browser window that contains that applet. Thus, every applet that interacts with JavaScript will include 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, and so you can continue the process and read their properties as well. The JSObject `getMember()` method returns the value of a named property, and 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]

Note two things about this code fragment above. First, that `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, that 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 (in this case an Applet) that it contains. The data conversion that occurs through the JSObject interface will be documented later in this section.

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 we've already seen. 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. This means that you can set JavaScript properties to values of types such as Applet, String, and JSObject, but you cannot set them to `boolean`, `int`, or `double`. Instead of setting properties or array elements to primitive Java values, you must use their corresponding Java object types, such as Boolean, Integer, and Double. Finally, on a related not, the `removeMember()` method allows you to delete the value of a named property from a JavaScript object.

Besides reading and writing properties and array elements from JavaScript objects, the JSObject class also 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, note that it is not possible to pass primitive Java values as arguments to a JavaScript method; instead you must use their 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 has one more very important method: `eval()`. This Java method of the JSObject works just like the JavaScript method of the JavaScript Object type--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. One reason is that it can be much simpler to use. Another is that since all the code is passed as a string, you can use a string representation of the data types you want, and 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()`:

    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);

### Using JSObjects in Applets

Example 19.5 shows the `init()` method of an applet that uses LiveConnect to interact with JavaScript.

#### Example 19.5: Using JavaScript from an Applet Method

    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.

#### Compiling applets that use the JSObject class

Any applet that interacts with JavaScript uses the netscape.javascript.JSObject class. In order to compile these applets, 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, then you may have to do something a little different--see the documentation from the vendor of your Java compiler or Java development environment.

The basic approach to tell the JDK compiler where to find classes is to set the CLASSPATH environment variable. This environment variable specifies a list of directories and zip files that the compiler should search for class definitions (in addition to its standard directory of system classes). Navigator 3.0 stores its class definitions in a file named `java_30`. The exact location of this file depends on what platform you use and also on how and where you installed the browser files. On a Unix system, the full path to this file will depend on where you installed Navigator, but will typically be something like:

    /usr/local/lib/netscape/java_30

On a Windows 95 system, the path will also depend on where you chose to install Navigator, but it will usually be something like:

    C:\ProgramFiles\Netscape\Navigator\Program\Java\Classes\Java_30

You may have to search a bit to locate this file on your system.

The `java_30` file, wherever it is located, is an uncompressed zip file of all the Java classes Navigator needs. The javac compiler can extract classes from zip files, and so you can tell the compiler where to find the netscape.javascript.JSObject class with lines like the following. For Unix systems:

    setenv CLASSPATH .:/usr/local/lib/netscape/java_30

And for Windows 95 systems:

    set CLASSPATH=.;C:\Program Files\Netscape\Navigator\Program\Java\Classes\Java_30

If this does not work for you, you may need to extract the `netscape/` directory from the `java_30` zip file, and install this directory somewhere like `/usr/local/lib/netscape_classes`. Then, you can include this unzipped directory in your CLASSPATH environment variable.

#### The MAYSCRIPT attribute

There is one further requirement before you can run an applet that interacts with JavaScript. As a security precaution, applets are not allowed to use JavaScript unless the web page author (who may be different than the applet author) explicitly gives the applet permission to do so. To give this permission, you must include the new `MAYSCRIPT` attribute in an applet's `<APPLET>` tag in the HTML file.

Example 19.5 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 with HTML code like the following:

    <APPLET code="CPUHog.class" width=300 height=300 MAYSCRIPT></APPLET>

If you do not remember to include the `MAYSCRIPT` tag, the applet will not be allowed to interact with JavaScript.

#### A complete example

Example 19.6 shows a complete example of a Java class that uses LiveConnect and the JSObject class to communicate with JavaScript. The class is a subclass of java.io.OutputStream, and is used to allow a Java applet to write HTML text into a newly created web browser window. An applet might want to do this because it provides a way to display formatted text, which is difficult to do with Java itself. Another important reason that an applet might want to display its output in a browser window is that this gives the user the ability to print the output or save it to a file, which are capabilities that applets themselves do not have.

#### Example 19.6: An OutputStream for Displaying HTML in a Browser Window

    import netscape.javascript.JSObject;    // these are the classes we'll use
    import java.applet.Applet;
    import java.io.OutputStream;
    // an output stream that sends HTML text to a newly created web browser window
    public class HTMLOutputStream extends OutputStream
    {
        JSObject main_window;        // the initial browser window
        JSObject window;             // the new window we create
        JSObject document;           // the document of that new window
        static int window_num = 0;   // used to give each new window a unique name
        // To create a new HTMLOutputStream, you must specify the applet that
        // will use it (this specifies a browser window) and the desired size
        // for the new window.
        public HTMLOutputStream(Applet applet, int width, int height)
        {
            // get main browser window from the applet with JSObject.getWindow()
            main_window = JSObject.getWindow(applet);
            // use JSObject.eval() to create a new window
            window = (JSObject)
                main_window.eval("self.open(''," +
                                 "'HTMLOutputStream" + window_num++ + "'," +
                                 "'menubar,status,resizable,scrollbars," +
                                 "width=" + width + ",height=" + height + "')");
            // use JSObject.getMember() to get the document of this new window
            document = (JSObject) window.getMember("document");
            // Then use JSObject.call() to open this document.
            document.call("open", null);
        }
        // This is the write() method required for all OutputStream subclasses.
        public void write(byte[] chars, int offset, int length)
        {
            // create a string from the specified bytes
            String s = new String(chars, 0, offset, length);
            // store the string in an array for use with JSObject.call()
            Object[] args = { s };
            // check to see if the window has been closed
            boolean closed = ((Boolean)window.getMember("closed")).booleanValue();
            // if not, use JSObject.call() to invoke document.write()
            if (!closed) document.call("write", args);
        }
        // Here are two variants on the above method, also required.
        public void write(byte[] chars) { write(chars, 0, chars.length); }
        public void write(int c) { byte[] chars = {(byte)c}; write(chars, 0, 1); }
        // When the stream is closed, use JSObject.call() to call Document.close
        public void close() { document.call("close", null); }
        // This method is unique to HTMLOutputStream. If the new window is
        // still open, use JSObject.call() to invoke Window.close() to close it.
        public void close_window()
        {
            boolean closed = ((Boolean)window.getMember("closed")).booleanValue();
            if (!closed) window.call("close", null);
        }
    }

### Data Conversion

At the beginning of this chapter we described the rules by which value are converted when JavaScript reads and writes Java fields and invokes Java methods. Those rules explained how the JavaScript JavaObject, JavaArray, JavaClass, and JavaMethod objects convert data, and they apply only to the case of JavaScript manipulating Java. When Java manipulates JavaScript, the conversion is performed by the Java JSObject, and the conversion rules are different. Figure 19.5 and Figure 19.6 illustrate this conversion.

#### Figure 19.5: Data conversions performed when Java writes JavaScript values

#### Figure 19.6: Data conversions performed when Java reads JavaScript values

The point to remember when studying these figures is that Java can only interact with JavaScript through the API provided by the JSObject class. This class allows only Java objects, not primitive values, to be written to JavaScript, and allows only Java objects to read from JavaScript. When writing JavaScript functions that will be invoked from Java, bear in mind that the arguments passed by Java will either be JavaScript objects from unwrapped Java JSObjects, or they will be JavaObjects. As we saw earlier in this chapter, JavaObjects behave somewhat differently than other types. For example, an instance of java.lang.Double behaves differently than a primitive JavaScript number or even a JavaScript Number object. The same caution applies when you are working with JavaScript properties that will have their values set by Java.

Keep in mind that one way to avoid the whole issue of data conversion is to use the `eval()` method of the JSObject class whenever your Java code wants to communicate with JavaScript. In order to do this, your Java code must convert all method arguments or property values to string form. Then the string to be evaluated can be passed unchanged to JavaScript, which can convert the string form of the data to the appropriate JavaScript data types.

Scripting Java with JavaScript |  | Working with Java in Internet Explorer