19.6 Using JavaScript from JavaHaving 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 ClassAll 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");
JSObject jsroot = JSObject.getWindow(this); jsroot.eval("parent.frames[1].document.write('Hello from Java!')");
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 AppletsExample 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 classAny 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
C:\ProgramFiles\Netscape\Navigator\Program\Java\Classes\Java_30 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
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 attributeThere 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> A complete exampleExample 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 ConversionAt 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. 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. |
|