JavaFX invoking Java methods using JavaScript - javascript

My application is completely styled in a web document form (HTML, CSS & JavaScript), and I'm only using JavaFX WebView to load it as a normal resource.
I would like to invoke a method from one of my classes (a Java code) using JavaScript.
Like for example, a simple Hello World to the console:
public class Hello {
public void world() {
System.out.println("Hello World!");
}
}
How can I invoke the world() method in this case?
So my code from the page is something like this:
<!-- onlick action from a button calls hello() function -->
<button onclick="hello();" value="Invoke"></button>
<script>
function hello() {
/* CODE WHICH INVOKE A JAVA METHOD */
}
</script>
Any way to achieve this?
UPDATE
Notice: For those people who were looking for a complete and simple example about how to achieve this, you can test all the following codes written below.
I've finally achieved my goal, thanks to sir #Oshan_Mendis' answer. This example is based from this tutorial from Oracle docs: 6 Making Upcalls from JavaScript to JavaFX.
But here, I'll be using my own code, the main goal is to call a method from Java code using JavaScript from the HTML page.
File contents:
Controller.java /* Controller class for WebView */
Hello.java /* Class in which method(s) will be invoked */
Main.java /* Main class (launches the application) */
main.fxml /* Main layout (WebView) */
index.html /* Main layout web page content */
1. Creating the Main-Class (Main.java)
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
/* The root layout of the application, an FXML contains the WebView layout. */
Parent root = FXMLLoader.load(Main.class.getResource("/main.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
2. Preparing the Main layout (main.fxml)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.web.WebView?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="Controller" <!-- The controller class for this layout -->
prefHeight="400.0"
prefWidth="300.0">
<children>
<!-- Given the webView ID to initiate the web page -->
<WebView fx:id="webView" />
</children>
</VBox>
3. Setting up the web page (Controller.java)
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;
public class Controller implements Initializable {
private WebEngine webEngine;
#FXML private WebView webView;
#Override
public void initialize(URL location, ResourceBundle resources) {
/* Load the web page URL (location of the resource) */
URL url = Controller.class.getResource("/index.html");
webEngine = webView.getEngine();
webEngine.load(url.toExternalForm());
/* Set the State listener as well as the name of the JavaScript object and its
* corresponding Java object (the class in which methods will be invoked) that
* will serve as the bridge for the two objects.
*/
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> observableValue, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
/* The two objects are named using the setMember() method. */
window.setMember("invoke", new Hello());
}
}
});
}
}
4. Preferred class and its method to invoke (Hello.java)
public class Hello {
public void world() {
System.out.println("Hello World!");
}
}
5. Main layout web page content (index.html)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
function helloWorld() {
/* JavaScript object name and the method to invoke */
invoke.world();
}
</script>
</head>
<body>
<!-- onlick event calls helloWorld() function -->
<button onclick="helloWorld()">INVOKE</button>
</body>
</html>
Note: You can perform other mouse-related events other than onclick event in this case like: onmouseenter, onmouseover, onmousemove, onmouseup, etc.. But I'm not really sure if these are the only supported events of invoking methods.

This is well explained in the Java API Documentation under Calling back to Java from JavaScript
public class JavaApplication {
public void exit() {
Platform.exit();
}
}
...
JavaApplication javaApp = new JavaApplication();
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", javaApp);
You can then refer to the object and the method from your HTML page:
Click here to exit application

Related

How can I add the external jar file into react native application and use as a plugin into the javascript file for both android and iOS

Actually, I have existing SDKs and I wanted to use that SDK in the react native app.
For android
I tried adding the jar file into the libs folder of /android/app/
Added dependencies into file /android/app/build.gradle
implementation fileTree(include: ['*.jar'], dir: 'libs')
But I did not get how can I use these jar files in my js file. How can I create the object and call the methods?
The main concern is how can I use the external java libraries in my react native app?
You should use native modules them are bridge between JS and native
https://reactnative.dev/docs/native-modules-android
Example
public class DummyModule extends ReactContextBaseJavaModule {
MyDummyClass dummy // this context
public DummyModule(final ReactApplicationContext reactContext){
super(reactContext);
}
#Override
// getName is required to define the name of the module represented in
// JavaScript
public String getName() {
return "DummyModule";
}
#ReactMethod
public void startMyClass() {
this.dummy = new MyDummyClass();
}
#ReactMethod
public void fooActionClass() {
if(this.dummy != null){
this.dummy.fooAction();
}
}
}
In your javascript code
import { NativeModules } from 'react-native';
const dummyModule = NativeModules.DummyModule;
dummyModule.startMyClass();
// Make sure that u call the action when the class is instanciated.
dummyModule.fooActionClass();
Usefull question as well Sending hashmao from java to react native

Static Assets Cache with Spring

I am developing a web application and intend to make use of the performance boost that caching resources give, but it comes with an important caveat. Whenever I updated a static file, users wouldn't see these changes immediately, and so had to disable the browser's cache in order to fetch the newest version. In order to fix this issue, I decided to add static assets versioning. Which works as intended with the following code.
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
// Costume made transformer to handle JS imports
.addTransformer(new JsLinkResourceTransformer())
.addTransformer(new CssLinkResourceTransformer());
}
#Bean
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
return new ResourceUrlEncodingFilter();
}
Everything was working as intended, except for one simple detail. JS imports were still loading the none versioned files. So something like import * from './myscrypt.js', would not work properly.
I had to implement my own resource transformer in order to avoid that new caveat. The implementation does it's job, and now my imports would fetch the right version, like import * from './myscript-149shdhgshs.js'. Then, I thought everything was fixed, but a new issue came up. Here is the scenario, which will make it easier to understand.
I load a page that includes script.js
Then Spring serve me with the correct version of the file script-v1.js
After that, script-v1.js imports functions from myscript.js
The browser fetch the right version of the script myscript-v1.js
The two of them get cached locally
I update myscript.js making a new version myscript-v2.js
I reload the page, but since script-v1.js was stored in cache, I load it with the old import myscript-v1.js, even though there is a new version
I just can't seem to make it work. Of course, I could simply stop using js modules and instead just load all the scripts at once, but that is not the solution I want to go for. Would there be a solution for js module versioning using Spring?
My way of solving this cached version will be using app version. If the project is built on Maven, I see you're using classpath resource for static file resolutions. Whenever there is a new change to js file, you will have new build and if you could change the version on every build, here is my workaround would look like.
pom.xml
<version>0.1.0</version>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
application.yml
build:
version: #project.version#
This will push version from pom.xml to application.yml both dev on IDE and built jar
Controller
I'm using mustache view resolver here.
#Controller
public class HelloController {
#Value("${build.version}")
private String version;
private String encodedVersion;
#PostConstruct
public void setup() {
encodedVersion = new String(Base64.getEncoder().encode(version.getBytes())).replace("=", "");
}
#RequestMapping("/home")
public ModelAndView home() {
ModelAndView mv = new ModelAndView();
mv.setViewName("home.html");
return mv;
}
#ModelAttribute("version")
public String getVersion() {
return encodedVersion;
}
}
home.html
<html>
<head>
<script type="text/javascript" src="/pop.js?cache={{version}}"></script>
<script type="text/javascript">
window.version = "{{version}}" // in case you need this somewhere
</script>
</head>
<body>
<h1>Home1</h1>
version: {{version}}
</body>
</html>
Manipulating existing js files
#Configuration
#AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class Config implements WebMvcConfigurer {
#Value("${build.version}")
private String version;
private String encodedVersion;
#PostConstruct
public void setup() {
encodedVersion = new String(Base64.getEncoder().encode(version.getBytes())).replace("=", "");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/").setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)).resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
.addTransformer(new ResourceTransformer() {
#Override
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain) throws IOException {
// Be aware of side effects changing line break
String result = new BufferedReader(new InputStreamReader(resource.getInputStream())).lines().collect(Collectors.joining("\n"));
result = result.replace("{{cacheVersion}}", encodedVersion);
return new TransformedResource(resource, result.getBytes());
}
});
}
}
pop.js
import mod1 from './mod1.js?cache={{cacheVersion}}';
function dis() {
console.log("hello")
}
Since the version is added as ModelAttribute it will be available in all request mapping. For every version, this will be changed and the way you pull files can be using this cache version variable.

How to identify web actions in JavaFX webview?

I need to catch the actions like clicks, drag, right-click, double click etc, that are done by the user on JavaFX web view. How to achieve it?
You need to use setOnMouseClicked(), setOnMouseDragged() methods on the WebView.
The following example prints Mouse Clicked! in the console for every Mouse Click event.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
WebView webView = new WebView();
webView.getEngine().load("http://www.stackoverflow.com");
webView.setOnMouseClicked(event -> System.out.println("Mouse Clicked!"));
Scene scene = new Scene(webView, 300, 275);
primaryStage.setTitle("Welcome");
primaryStage.setScene(scene);
primaryStage.show();
}
}

how to add/access a JS compiled from GWT to/in another external html/JS project

I am tasked to compile the gwt project(which doesnot include HTML CSS) into JS files and add the same to an external JS/HTML file( which is in different project).
here is the java code which has to be compiled:
1. Client class:
package com.dell.supportassist.gwt.collectionReport.client;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.Exportable;
#Export("HelloWorld")
public class HelloWorld implements Exportable {
public String sayHello(){
return "Hello";
}
}
EntryPoint Class:
package com.dell.supportassist.gwt.collectionReport.client;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.Exportable;
#Export("HelloWorld")
public class HelloWorld implements Exportable {
public String sayHello(){
return "Hello";
}
}
My issue is, once the above gwt project/classes are complied, i want to access 'sayHello()' method in ma external javascript like this:
var person = hello.sayHello();
system.log(person);
But this is throwing a run time error saying 'hello' is not defined.
P.S I am trying to use the GWT compiled JS in an external HTML, JS Present in Durandaljs Framework.
Haven't done this kind of stuff for quite some time but you could achieve this using JSNI, which allows you to write native JS in GWT. From JSNI you can reference your GWT methods. That way you could define a function (on window, most typically) which would then become available to regular JS. From this JSNI-method you can reference your GWT/Java code.
Code example:
public class MyModule implements EntryPoint {
static {
export();
}
/**
* Makes our setData method accessible from plain JS
*/
private static native void export() /*-{
$wnd.setData = #my.package.MyModule::setData(Lcom/google/gwt/core/client/JavaScriptObject;);
}-*/;
private static void setData(JavaScriptObject javaScriptObject) {
// this method is now reachable as window.setData
}
}

How to expose class functionality in GWT

I have a class library written in Java and want to convert it to Javascript. All methods are pretty simple and mostly have to do with manipulating collections. I have this one class, GameControl, which I could instantiate and I want its methods exposed to other Javascript code on the page.
I thought to use GWT. I have a running project in GWT which compiles, but I can't figure out how to expose my instance (+functionality) of the GameControl class.
I thought using JSNI to expose my object should work, but it didn't. This is the short version of how it look like right now:
GameEntryPoint.java
import com.google.gwt.core.client.EntryPoint;
public class GameEntryPoint implements EntryPoint {
private GameControl _gameControl;
#Override
public void onModuleLoad() {
_gameControl = new GameControl();
expose();
}
public native void expose()/*-{
$wnd.game = this.#game.client.GameEntryPoint::_gameControl;
}-*/;
}
GameControl.java
package game.client;
public class GameControl {
public boolean isEmpty(int id){
// does stuff...
return true;
}
}
So, GWT indeed compiles the code, and I see that there is a GameControl_0 object being built and set into $wnd.game, but no isEmpty() method to be found.
My expected end result is to have a window.game as an instance of GameControl with all public methods GameControl exposes.
How can I do this?
Edit
As per #jusio's reply, using JSNI to expose window properties explicitly worked, but it was too verbose. I'm trying the gwt-exporter solution. Now I have
GameEntryPoint.java
package game.client;
import org.timepedia.exporter.client.ExporterUtil;
import com.google.gwt.core.client.EntryPoint;
public class GameEntryPoint implements EntryPoint {
#Override
public void onModuleLoad() {
ExporterUtil.exportAll();
}
}
RoadServer.java
package game.client;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;
#ExportPackage("game")
#Export("RoadServer")
public class RoadServer implements Exportable {
int _index;
int _id;
public RoadServer(int index,int id){
this._id=id;
this._index=index;
}
}
but still none of the code is exported (specifically not RoadServer).
You have exposed only instance of the GameControl. If you want to expose other methods, you'll have to expose them as well.
For example:
public native void expose()/*-{
var control = this.#game.client.GameEntryPoint::_gameControl;
var gameInstance = {
gameControl: control,
isEmpty:function(param){
control.#game.client.GameEntryPoint::isEmpty(*)(param);
}
}
$wnd.game = gameInstance;
}-*/;
Also there is a framework called gwt-exporter, it might make things easier for you
This may help.
http://code.google.com/p/gwtchismes/wiki/Tutorial_ExportingGwtLibrariesToJavascript_en

Categories