Reputation:
I am calling a Java method from JavaScript in a JavaFx WebView. The Java method does some work, i.e. does not return immediately. What is a natural way to wait for the method to finish from JavaScript?
Upvotes: 0
Views: 1079
Reputation: 209408
Since everything is executed on the FX Application Thread, the Java method you call needs to run the long-running process on a background thread (otherwise you will make the UI unresponsive). You can pass the method a Javascript function to call when it is complete: note that this javascript function needs to be called on the FX Application Thread. One way is to wrap the call in Platform.runLater()
, but using a Task
makes things a bit cleaner.
Here is a SSCCE:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewCallbackTest extends Application {
private static final String HTML =
"<html>"
+ " <head>"
+ " <script>"
+ ""
+ " function doCall() {"
+ " javaApp.doLongRunningCall('updateResults');"
+ " }"
+ ""
+ " function updateResults(results) {"
+ " document.getElementById('results').innerHTML = results ;"
+ " }"
+ ""
+ " </script>"
+ " </head>"
+ " <body>"
+ " <div>"
+ " Result of call:"
+ " </div>"
+ " <div id='results'></div>"
+ " </body>"
+ "</html>";
private Button button;
private JSObject window;
@Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
webView.getEngine().loadContent(HTML);
BorderPane root = new BorderPane(webView);
window = (JSObject) webView.getEngine().executeScript("window");
window.setMember("javaApp", this);
button = new Button("Run process");
button.setOnAction(e -> webView.getEngine().executeScript("doCall()"));
HBox controls = new HBox(button);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
root.setBottom(controls);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public void doLongRunningCall(String callback) {
Task<String> task = new Task<String>() {
@Override
public String call() throws InterruptedException {
Thread.sleep(2000);
return "The answer is 42";
}
};
task.setOnSucceeded(e ->
window.call(callback, task.getValue()));
task.setOnFailed(e ->
window.call(callback, "An error occurred"));
button.disableProperty().bind(task.runningProperty());
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
}
(There may be an easier way than this: I am not an expert on Webview Javascript <-> Java communication, but this seems OK to me.)
Upvotes: 2