Deploying JavaFX Applications

Previous
Next

8 JavaFX and JavaScript

This chapter shows how JavaFX applications can be accessed from JavaScript and vice versa.

A JavaFX application can communicate with the web page in which it is embedded by using a JavaScript engine. The host web page can also communicate to embedded JavaFX applications using JavaScript.


Note:

To a large extent, this functionality is based on the Java-to-JavaScript communication bridge that is implemented in the Java plug-in. Therefore most of the available documentation and examples for Java applets are also applicable to JavaFX applications. For more information about the Java implementation, see the Java LiveConnect documentation.


This page contains the following sections.

8.1 Accessing a JavaFX Application from a Web Page

To access a JavaFX application from JavaScript, the first step is to get a reference to a JavaScript object representing the JavaFX application. The easiest way to get the reference is to use a standard JavaScript getElementById() function, using the identifier that was specified in the id attribute of the Ant <fx:deploy>, as shown in Example 8-1.

Example 8-1 Use JavaScript to Access an Application Object ID

var fxapp = document.getElementById("myMapApp") 

The result corresponds to the main class of the JavaFX application.

By getting the reference to a JavaScript object, you can use JavaScript code to access any public methods and fields of a Java object by referencing them as fields of the corresponding JavaScript object. After you have the fxapp reference, you can do something similar to the following:

var r = fxapp.doSomething()

The implementation of the doSomething() method in Java code returns a Java object. The variable r becomes a reference to the Java object. You can then use code such as r.doSomethingElse() or fxapp.dosomethingWithR(r).

You can access static fields or invoke static methods for classes loaded by a given application, by using a synthetic Packages keyword attached to the application object. You can use the same approach to create new instances of Java objects. For example, Example 8-2 contains Java code, and Example 8-3 contains JavaScript that interacts with that code. Look at both examples to see how they work together.

Example 8-2 Java Code Example

package testapp;
 
public class MapApp extends Application {
    public static int ZOOM_STREET = 10;

    public static class City {
        public City(String name) {...}
        ...
    }
 
    public int currentZipCode;
 
    public void navigateTo(City location, int zoomLevel) {...}
    ....
}

The JavaScript snippet in Example 8-3 passes several values to the Java code in Example 8-2. Before these values are used in the Java code, they are automatically converted to the closest Java type.

Example 8-3 JavaScript Code for Example 8-2

function navigateTo(cityName) {
    //Assumes that the Ant task uses "myMapApp" as id for this application
    var mapApp = document.getElementById("myMapApp");
    if (mapApp != null) {
        //City is nested class. Therefore classname uses $ char 
        var city = new mapApp.Packages.testapp.MapApp$City(cityName);
        mapApp.navigateTo(city, mapApp.Packages.testapp.MapApp.ZOOM_STREET);
        return mapApp.currentZipCode;
    }
    return "unknown";
}
window.alert("Area zip: " + navigateTo("San Francisco"));

The JavaScript string, numeric, and Boolean objects can be converted into most of the Java primitive types—Boolean, byte, char, short, int, long, float, and double—and java.lang.String.

For JavaScript objects representing Java objects (in other words, objects that have previously been returned from Java), conversion results in extracting a reference to that Java object.

Conversion into one and multidimensional arrays is supported according to rules similar to rules for conversion of individual objects. If conversion cannot be performed successfully, then the JavaScript engine raises an exception.

All Java objects returned to the web browser are associated with a particular JavaFX application instance. References held by the JavaScript engine to a Java objects act as persistent references, preventing that Java object from being garbage-collected in the hosting JVM. However, if a particular application is destroyed, for example by leaving the web page hosting the application or by detaching the application from the HTML DOM tree, then references are immediately invalidated and further attempts to use those object in JavaScript will raise exceptions.

For more information about data type conversion and object lifetimes, see

http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS


Note:

If a Java object has overloaded methods, in other words if it has multiple methods with the same name, but different sets of argument types, then the heuristic will be adopted of using the method with the closest types. For information, see the Java LiveConnect documentation.

The general recommendation is to avoid overloaded methods if you plan to use them from JavaScript code.


8.2 Accessing the Host Web Page from an Embedded JavaFX Application

JavaFX applications can call the following JavaScript components:

  • Functions

  • The get, set, and remove fields of JavaScript objects

  • The get and set elements of JavaScript arrays

JavaFX applications can also evaluate snippets of JavaScript code. Through the JavaScript DOM APIs, JavaFX applications can modify the web page dynamically by adding, removing and moving HTML elements.

To bootstrap JavaFX-to-JavaScript communication, the JavaFX application must get a reference to the JavaScript window object containing the application. This reference can be used for subsequent operations such as evaluation, function calls, and fetches of variables.

Both the main and preloader application can get this reference by accessing the HostServices class in the JavaFX API and requesting getWebContext(), as shown in Example 8-4.

Example 8-4 Access the HostServices Class from JavaFX Code

public class MyApp extends Application {
    private void communicateToHostPage() { 
        JSObject jsWin = getHostServices().getWebContext();  
        //null for non-embedded applications
        if (jsWin != null) {
            //use js
            ...      
        }
    }
    ...
}

All instances of JavaScript objects, including references to the DOM window, appear within Java code as instances of netscape.javascript.JSObject.

Example 8-5 shows how to use JavaScript to implement a function to resize an embedded application with id='myMapApp' at runtime.

Example 8-5 Use JavaScript to Resize an Application in the Browser

public void resizeMyself(int w, int h) {
    JSObject jsWin = getHostServices().getWebContext();
    if (jsWin != null) {
        jsWin.eval("var m = document.getElementById('myMapApp');" +
            "m.width=" + w + "; m.height=" + h + ";");
    } else {
        // running as non embedded => use Stage's setWidth()/setHeight()
    }
}

8.3 Advanced topics

JavaFX applications embedded in a web page can call JavaScript methods in a web page after the init() method is called for the preloader or main application class.

JavaScript can access JavaFX applications at any time, but if the application is not ready yet, then this request may be blocked until the application is ready. Specifically, this will happen if the init() method of the main application class has not finished yet and the main application did not perform calls to the web page itself. A JavaScript call from the preloader does not fully unblock JavaScript-to-Java communication.

Most browsers use single-threaded JavaScript engines. This means that when blocking occurs, the host web page and the browser appear to be frozen.

To access a JavaFX application from the host web page early and avoid blocking, either notify the web page when the application is ready by calling a Java function from the application, or use an onJavascriptReady callback in the Ant task.

Example 8-6 shows an HTML template for an Ant task that uses an onJavascriptReady callback to call the doSomething() method in the main application without blocking the browser.

Example 8-6 HTML Input Template for an Ant Task

<html>
    <head>
        <!-- template: code to load DT JavaScript will be inserted here -->
        #DT.SCRIPT.CODE#
        <!-- template: code to insert application on page load will be 
         inserted here -->
        #DT.EMBED.CODE.ONLOAD#
 
        <script>
            function earlyCallFunction(id) {
                //it is safe to call application now
                var a = document.getElementById(id);
                if (a != null) a.doSomething();
            }
        </script>
    </head>
    <body>
        <!-- application is inserted here -->
        <div id="ZZZ"></div>
    </body>
</html>

Example 8-7 shows the relevant part of the Ant task used to generate an HTML page from the template in Example 8-6. For this example, it is assumed that the template has the path src/web/test_template.html.

Example 8-7 Ant <fx:deploy> Task to Generate an HTML Page from a Template

<fx:deploy placeholderId="ZZZ" ...>
    ....
    <fx:template file="src/web/test_template.html"
            tofile="dist/test.html"/>
    <fx:callbacks>
        <fx:callback name="onJavascriptReady">earlyCallFunction</fx:callback>
    </fx:callbacks>
</fx:deploy>

8.4 Threading

Java code called from JavaScript is executed on a special thread that is not the JavaFX application thread. Use the Platform.runLater() method in the JavaFX code to ensure that something is executed on the JavaFX application thread.

In general, return as quickly as possible from functions that are called from JavaScript. In most modern browsers, JavaScript engines are single-threaded. If the call sticks, then the web page can appear frozen, and the browser will be unresponsive. In particular, it is recommended that you avoid writing code to wait for work to be done on a JavaFX application thread. If JavaScript code depends on the result of this work, then it is recommended that you use a callback from Java to notify the JavaScript code of the result of the execution of the work.

Example 8-8 shows an example of code to avoid in JavaScript.

Example 8-8 Naive implementation Blocking JavaScript Thread

function process(r) {
    window.alert("Result: "+r);
}
 
var result = myApp.doSomethingLong();
process(result);

Example 8-9 shows a better pattern to follow in JavaScript code.

Example 8-9 A Better Implementation of Example 8-8

function process(r) {
    window.alert("Result: "+r);
}
  
myApp.doSomethingLong(function(r) {process(r);});

Example 8-10 shows a better example in Java code.

Example 8-10 Java Code Using a Callback

public void doSomethingLong(JSObject callback) {
    Object result;
    //do whatever is needed to get result
 
    //Invoke callback 
    //  callback is a function object, and every function object 
    // has a "call" method
    Object f[] = new Object[2];
    f[0] = null; //first element is object instance but this is global function
                 //not applying it to any specific object
    f[1] = new String(result); //real argument
    callback.call("call", f);
}

Java code can call JavaScript from any thread, including the JavaFX application thread. However, if the JavaScript engine in the browser is busy, then a call to JavaScript may stick for some time. If there is a call on the JavaFX application thread, then it may make your application appear frozen, because it will not be able to update the screen and handle user events. It is recommended that you offload execution of LiveConnect calls from the JavaFX application thread.

8.5 Security

JavaScript code on the web page can always make JavaScript-to-Java calls against an application on the page, and it can access all public methods and fields of Java classes loaded by the application. However, when a JavaScript-to-Java call is made, it is treated as called from the sandbox environment. Moreover, if the HTML document and the application originate from different sites, then JavaScript on the web page cannot cause any network connections to be made on its behalf.

Aside from this restriction, calling Java from JavaScript does not have any other consequences if the application is running in the sandbox. However, if the application is signed and trusted and therefore can request elevated permissions, then a call to a Java method from JavaScript is executed in the sandbox without elevated permissions. If elevated permissions are needed, then AccessController.doPrivileged in the Java API can be used to request them in the trusted code.

Developers should be careful not to expose APIs in their applications that would accidentally confer additional privileges on untrusted JavaScript code. Developers who must grant elevated privileges to JavaScript code are encouraged to serve their applications over verifiable HTTPS connections, and perform checks to ensure that the document base of the web page hosting the application is the same as the expected origin of the application's code.

8.6 Tab Pane Example

This section contains a sample that demonstrates how to use communication between JavaFX and JavaScript to integrate JavaFX web applications with the browser. Example 8-11 shows a JavaFX application that creates a tab pane on a web page, with 20 tabs.

Example 8-11 Create Tabs on the Embedding Web Page

public class TabbedApp extends Application {
    Group root = new Group();
    TabPane tabPane = new TabPane();
            
    public void init() {
        // Prepare tab pane with set of tabs        
        BorderPane borderPane = new BorderPane();
        tabPane.setPrefSize(400, 400);
        tabPane.setSide(Side.TOP);
        tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
        
        for(int i=1; i<=20; i++) {
            final Tab t = new Tab("T" + i);
            t.setId(""+i);            
            Text text = new Text("Tab "+i);
            text.setFont(new Font(100));
            BorderPane p = new BorderPane();
            p.setCenter(text);
            t.setContent(p);
            tabPane.getTabs().add(t);
        }
        borderPane.setCenter(tabPane);
        root.getChildren().add(borderPane);        
    }
 
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}

This application can be further improved to save the history of visited tabs into the browser history. This enables users to click the Back and Forward buttons in the browser to move between tabs.

The implementation is based on the onhashchange event introduced in HTML 5 and described at

http://www.whatwg.org/specs/web-apps/current-work/#event-hashchange

The JavaScript technique used by AJAX applications to achieve a similar effect is to save a reference to the current selection in the hash part of the document URL. When the user clicks the Back button, the URL is updated, and a selection state can be extracted that must be restored.

To implement this solution, two new methods are added to the sample: onNavigate() and navigateTo(). The onNavigate() method is called whenever a new tab is selected. It delivers information about the new selection to the web page by calling the JavaScript method navigateTo() and passing the tab ID to it. The JavaScript code saves the tab ID in the URL hash.

The navigateTo() method is responsible for reverse synchronization. After the web page URL is changed, this method is called with the ID of the tab to be selected.

Example 8-12 shows the updated code of the application. The code that is different from Example 8-11 appears in bold.

Example 8-12 Improved Application that Saves Tab History

public class TabbedApp extends Application {
    Group root = new Group();
    TabPane tabPane = new TabPane();
            
    public void init() {
        // Prepare tab pane with set of tabs        
        BorderPane borderPane = new BorderPane();
        tabPane.setPrefSize(400, 400);
        tabPane.setSide(Side.TOP);
        tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
        
        for(int i=1; i<=20; i++) {
            final Tab t = new Tab("T" + i);
            t.setId(""+i);            
            Text text = new Text("Tab "+i);
            text.setFont(new Font(100));
            BorderPane p = new BorderPane();
            p.setCenter(text);
            t.setContent(p);
            
            // When tab is selected, notify web page to save this in the
            // browser history
            t.selectedProperty().addListener(new ChangeListener<Boolean>() {
                public void changed(ObservableValue<? extends Boolean> ov,
                        Boolean tOld, Boolean tNew) {
                    if (Boolean.TRUE.equals((tNew))) {
                        onNavigate(t.getId());                                        
                    }
                }                
            });
            tabPane.getTabs().add(t);
        }
        borderPane.setCenter(tabPane);
        root.getChildren().add(borderPane);        
}
 
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
 
    public void navigateTo(String tab) {
        for (Tab t: tabPane.getTabs()) {
            if (tab.equals("#"+t.getId())) {
                tabPane.getSelectionModel().select(t);
                return;
            }
        }
    }
    
    private void onNavigate(String tab) {
      JSObject jsWin = getHostServices().getWebContext();  
      // Null for nonembedded applications
      if (jsWin != null) {
          //use js
          jsWin.eval("navigateTo('" + tab + "')");
      }
    }    
}

Part of the implementation logic is in the HTML page. Example 8-13 shows a page that is used as an input template in an Ant script. When the Ant script is run, it inserts code to embed the JavaFX application next to the custom JavaScript code. For more information about input templates, see <fx:template>.

The implementation of JavaScript functions is straightforward. The onhashchange attribute of the <body> tag is used to subscribe to notifications of updates of the hash part of the URL. After the event is obtained, the JavaFX application is embedded in the web page, and the navigateTo() method is called.

If the application calls with an update on the selected tab, it is saved to the hash part of the URL.

Example 8-13 HTML Template Used as Input to the Ant Script

<html>
    <head>
        <!-- template: code to load DT javascript will be inserted here -->
        #DT.SCRIPT.CODE#
        <!-- template: code to insert application on page load will be 
            inserted here -->
        #DT.EMBED.CODE.ONLOAD#
 
        <script>
            function hashchanged(event) {
                var a = document.getElementById('tabbedApp');
                if (a != null) {
                    try {
                        a.navigateTo(location.hash);                
                    } catch (err) {
                        alert("JS Exception: " + err);
                    }
                }
            }
 
            function navigateTo(newtab) {
                if (window.location.hash != newtab) {
                    window.location.hash = newtab;                    
                }
            } 
        </script> 
    </head>
    <body onhashchange="hashchanged(event)">
        <h2>Test page</h2>
        <!-- Application will be inserted here -->
        <div id='javafx-app-placeholder'></div>
    </body>
</html>

For completeness, Example 8-14 shows the Ant script used to deploy this sample. The application is created with the ID tabbedApp. The JavaScript code uses this ID to find the application on the page. and the HTML template uses it to embed the application into the custom HTML page that is produced by the Ant task.

Example 8-14 Ant Script to Package the Application

<fx:application id="tabbedApp"
        name="Example of browser integration"
        mainClass="docsamples.TabbedApp"/>
        
<fx:jar destfile="dist/docsamples/tabbedapp.jar">
    <fx:application refid="tabbedApp"/>
    <fileset refid="appclasses"/>
</fx:jar>
        
<fx:deploy width="400" height="400"
        outdir="dist-web"
        outfile="BrowserIntegrationApp">
    <fx:info title="Doc sample"/>
    <fx:application refid="tabbedApp"/>
    <fx:template
            file="src/template/TabbedApp_template.html"
            tofile="dist-web/TabbedApp.html"/>
    <fx:resources>
        <fx:fileset requiredFor="startup" dir="dist/docsamples">
            <include name="tabbedapp.jar"/>
        </fx:fileset>
    </fx:resources>
</fx:deploy>
Previous
Next