I retrieve an object from Java to my JavaScript in WebView, and want to call methods on it. Doesn't work.
I have a JavaFX WebView, which displays an html page. This page includes JavaScript. I set a Java object as member variable on the "window", so that I can call methods on it from JavaScript, as per the documentation:
WebEngine engine = webView.getEngine();
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("manager", new Manager());
That's what I find in all examples. Assume the Manager class has a method, "doSomething()", I can call that method, on my Manager Java object, from JavaScript:
<script>
function onclick() {
manager.doSomething();
}
</script>
This works just fine.
However, assume the doSomething() returns an object, e.g. a Todo which has a method "getDescription()", which just returns a string.
It should look like this:
<script>
function onclick() {
var todo = manager.doSomething();
var desc = todo.getDescription();
document.getElementById("description-label").innerHTML = desc;
}
</script>
But that doesn't work for me. I cannot execute "todo.getDescription()". The WebView never prints any errors anywhere, as far as I can tell, no matter what I break, so it's tough to figure out the problem.
As I understand the documentation here https://docs.oracle.com/javafx/2/api/javafx/scene/web/WebEngine.html
with this explanation:
... a JavaRuntimeObject is created. This is a JavaScript object that acts as a proxy for the Java object, in that accessing properties of the JavaRuntimeObject causes the Java field or method with the same name to be accessed
My approach should work.
Eventually I just want to retrieve a collection of objects, and display their data in a table.
What am I doing wrong?
Calling Java from JavaScript
The example:
Label
and a WebView
.JSObject
with the JavaFX app.Some things to note:
Use current documentation on WebEngine
(for JavaFX 19), don't use outdated documentation from JavaFX 2.
WebEngine
within a modular environment. The additional information on modularity will be critical for some applications.To access the WebView
, use:
requires javafx.web;
To access the JSObject
which bridges Java and the WebView, use:
requires jdk.jsobject;
To have your code accessible to JavaScript executing in WebView
, use:
opens <package-with-your-code> to javafx.web
This is required because, internally, the WebEngine
will use reflection on your code to allow JavaScript to call it.
Make sure the document is loaded before performing actions on it.
You don't (as far as I can tell) need to provide transitive access to the objects you return to the webview. For instance, in the JavaScript I call, app.getLabel().getText();
, but just opening the package with my app to javafx.web
is sufficient, I don't need to open the javafx.scene.control
package to javafx.web
. I'm not exactly sure why things work like that, but that appeared to be the case.
This JavaScript will also work:
var label = app.getLabel();
document.getElementById('text-from-java').innerHTML = label.getText();
But this next JavaScript will not work, because the type Label
is not known to JavaScript, even though when you use the var
form you can execute methods on the var which is a javafx.scene.control.Label
:
Label label = app.getLabel();
document.getElementById('text-from-java').innerHTML = label.getText();
com/example/bridge/BridgeApp.java
package com.example.bridge;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class BridgeApp extends Application {
private final Label label = new Label("Text copied from JavaFX to WebView");
private static final String HTML = // language=HTML
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<span id="text-from-java"></span>
</body>
</html>
""";
@Override
public void start(Stage stage) {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.documentProperty().addListener((observable, oldValue, newDocument) -> {
JSObject window = (JSObject) engine.executeScript(
"window"
);
window.setMember(
"app",
this
);
engine.executeScript(
"document.getElementById('text-from-java').innerHTML = app.getLabel().getText();"
);
});
engine.loadContent(HTML);
VBox layout = new VBox(
10,
label,
webView
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public Label getLabel() {
return label;
}
public static void main(String[] args) {
launch();
}
}
module-info.java
module com.example.bridge {
requires javafx.controls;
requires javafx.web;
requires jdk.jsobject;
opens com.example.bridge to javafx.graphics, javafx.web;
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>bridge</artifactId>
<version>1.0-SNAPSHOT</version>
<name>bridge</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>19</source>
<target>19</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Calling JavaScript from Java, passing data in the executeScript
call
The example in the answer is just for demo purposes, to demonstrate how to call Java code from a WebView. The same result could be accomplished by executing a script directly on the webengine as shown below, but then it wouldn't be demonstrating calling Java from JavaScript as requested in the question.
engine.executeScript(
"document.getElementById('text-from-java').innerHTML = '" + label.getText() + "';"
);