This topic shows how Java and JavaFX applications can be accessed from JavaScript code, and how JavaScript code can be accessed from Java and JavaFX applications.
An 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 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, much 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 topic contains the following sections.
To access a Java or JavaFX application from JavaScript, the first step is to get a reference to a JavaScript object that represents the application. The easiest way to get the reference is to use a standard JavaScript getElementById()
function, using the name attribute of the applet tag, as shown in Example 16-1. The <fx:deploy> ant task automatically generates the applet tag, and the name is taken from the id
attribute of the <fx:application>.
Example 16-1 Use JavaScript to Access an Application Object ID
var app = document.getElementById("myMapApp")
The result corresponds to the main class of the 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 the Java objects as fields of the corresponding JavaScript object. After you have the app
reference, you can do something similar to the following code statement:
var r = app.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 app.dosomethingWithR(r)
. For example, Example 16-2 contains Java code, and Example 16-3 contains JavaScript that interacts with that code. Look at both examples to see how they work together.
Example 16-2 Java Code Example
package testapp; public class MapApp extends Application { public int ZOOM_STREET = 10; public class City { public City(String name) {...} ... } public int currentZipCode; public void navigateTo(City location, int zoomLevel) {...} .... public City getCity(String cityName) {...} .... }
The JavaScript snippet in Example 16-3 passes several values to the Java code in Example 16-2. Before these values are used in the Java code, they are automatically converted to the closest Java type.
Example 16-3 JavaScript Code for Example 16-2
function navigateTo(cityName) { //Assumes that the applet tag uses "myMapApp" as the name for this application var mapApp = document.getElementById("myMapApp"); if (mapApp != null) { var city = mapApp.getCity(cityName); mapApp.navigateTo(city, 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 typesBoolean, byte, char, short, int, long, float, and doubleand 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 application instance. References held by the JavaScript engine to Java objects act as persistent references, which prevent 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 raise exceptions.
For more information about data type conversion and object lifetimes, see
https://www.oracle.com/java/technologies/javase/liveconnect-docs.html#JS_JAVA_CONVERSIONS
Note: If a Java object has overloaded methods, which are multiple methods with the same name but different sets of argument types, then the method with the closest types is used. For information, see the Java LiveConnect documentation.The general recommendation is to avoid overloaded methods if you plan to use them from JavaScript code. |
See Invoking JavaScript Code From an Applet in the Java Tutorial for information on communication between the applet and host web page.
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 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 16-4.
Example 16-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 16-5 shows how to use JavaScript to implement a function to resize an embedded application with id='myMapApp'
at runtime.
Example 16-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() } }
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 code can access Java applications at any time, but if the application is not ready yet, then the request might be blocked until the application is ready. Specifically for JavaFX applications, this happens 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. When blocking occurs, the host web page and the browser appear to be frozen.
To access an 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 16-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 16-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 16-7 shows the relevant part of the Ant task used to generate an HTML page from the template in Example 16-6. For this example, it is assumed that the template has the path src/web/test_template.html
.
Example 16-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>
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 is unresponsive. Specifically, avoid writing code that waits for work to be done on a JavaFX application thread. If JavaScript code depends on the result of this work, use a callback from Java to notify the JavaScript code of the result of the execution of the work.
Example 16-8 shows an example of code to avoid in JavaScript.
Example 16-8 Naive implementation Blocking JavaScript Thread
function process(r) { window.alert("Result: "+r); } var result = myApp.doSomethingLong(); process(result);
Example 16-9 shows a better pattern to follow in JavaScript code.
Example 16-9 A Better Implementation of Example 16-8
function process(r) { window.alert("Result: "+r); } myApp.doSomethingLong(function(r) {process(r);});
Example 16-10 shows a better example in Java code.
Example 16-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 might stick for some time. If there is a call on the JavaFX application thread, then it might make your application appear frozen, because it is not able to update the screen and handle user events. To avoid this situation, offload execution of LiveConnect calls from the JavaFX application thread.
JavaScript code on the web page can always make calls to an application on the page. JavaScript code can also access all public methods and fields of Java classes loaded by the application. However, when a JavaScript-to-Java call is made, the call is treated as a call from the sandbox environment. Also, 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 has requested elevated permissions, then a call to a Java method from JavaScript is executed in the sandbox without elevated permissions, and a security warning is issued. If elevated permissions are needed, then AccessController.doPrivileged
in the Java API can be used to request them in the trusted code.
Be careful not to expose APIs in your applications that would accidentally confer additional privileges on untrusted JavaScript code. If you must grant elevated privileges to JavaScript code, serve your application 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.
This section contains a sample that demonstrates how to use communication between JavaFX and JavaScript to integrate JavaFX web applications with the browser. Example 16-11 shows a JavaFX application that creates a tab pane on a web page, with 20 tabs.
Example 16-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. This method 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 16-12 shows the updated code of the application. The code that is different from Example 16-11 appears in bold.
Example 16-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 16-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 16-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 16-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 16-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>