Why AngularJS+FX?
(I am very sorry for formatting. Will search for another blogging platform)Bear has an UI to monitor tasks being run on the hosts and write bear script snippets. When choosing a UI framework for the Bear, I considered several options which were JavaFX, AngularJS app with Play! framework backend or an AngularJS for Desktop via the WebView in JavaFX.
Why AngularJS+FX
- Compared to the JavaFX app AngularJS+FX has Twitter Bootstrap (including many themes) and AngularJS. HTML provides a more rapid developing experience than FXML.
- The most rapid development
- No need to study yet another framework (JavaFX)
- No external dependencies on the backend
- Migrating to a server app should not be a pain
- A nice experience being a pioneer :-)
Binding your JS app to Java
Prerequisites: JDK 8 ea109+. Avoid using JDK 7 for the WebViews as it has Font rendering problems.
Below is the Hello World for JavaFX which is also demo of a bug in JavaFX (at Github).
public class TestOverloadingApp extends Application { private WebEngine webEngine; @Override public void start(Stage stage) throws Exception { final WebView webView = new WebView(); webEngine = webView.getEngine(); Scene scene = new Scene(webView); stage.setScene(scene); stage.setWidth(1200); stage.setHeight(600); stage.show(); // this is a proper way to load a page from your resources // if your testOverloading.html references an image with <img src="images/test.jpg"> // then your test.jpg must be placed in /javafx/overloading1/images/test.jpg webEngine.load(TestOverloadingApp.class.getResource("/javafx/overloading1/testOverloading.html").toURI().toURL().toString()); webEngine.setOnAlert(new EventHandler<WebEvent<String>>() { @Override public void handle(WebEvent<String> stringWebEvent) { // this is a simple way to debug your app - call alert('this will into your Java log') on the JS side System.out.println("alert: " + stringWebEvent.getData()); } }); webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() { @Override public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State t1) { System.out.println("[JAVA INIT] setting..."); if (t1 == Worker.State.SUCCEEDED) { // get the window object is a global variable in JS JSObject window = (JSObject) webEngine.executeScript("window"); // bind our Java objects as fields in the window object // in JS this would look like // window.foo = new Foo() window.setMember("fooWhichIsOK", new FooWhichIsOk()); window.setMember("foo", new Foo()); } } }); } public static void main(String[] args) { launch(args); } }
HTML
In HTML you would call your provided Java instances:
<script> function testOk(){ // this invokes a method on a Java bean registered previously. window.foo.foo('test'); // log an error alert('have called a method'); } </script> <body> <div id="okTest" onclick="testOk()">Run OK Test</div> </body>
Current bindings limitations
After a number of tries I must say that the binding usage is quite limited. Below are the results of my work with JDK 8 ea109Calling Java from JavaScript
- Consider using only primitive types as parameters of java methods (i.e. int, char, String, arrays, String[])
- For anything more complex parse JSON String
- Overridden methods don't work
For JSON conversion I used facade beans which receive calls from JS and convert data to Java. It's similar to Controllers in frameworks like Grails or AngularJS.
Calling JavaScript from Java
This is pretty straight-forward. Just call it like this
webEngine.evaluate(window.yourObject.receiveJSON('{your:"JSON object here", with: "your params"}'))
Firebug Lite
You will soon notice that WebView lacks the development tools. To enable FirebugLite, add this script to your HTML:
<script src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'>
This is the only version which worked for me.
Init order
Init order is the following.
- Document is loaded into the WebView.
- $(document).ready(...) is called inside the WebEngine
- JavaFX change listener is called.
So your JavaFX init code is always called the last. Below is an example of how this could done.
var Java = {}; JS: Java.init = function(window){ Java.log("initializing Java..."); Java.OpenBean = window.OpenBean; //registers the bindings }; Java.mode = navigator.userAgent.match(/Chrome\/\d\d/) ? 'Chrome' : (navigator.userAgent.match(/Firefox\/\d\d/) ? 'FF' : 'FX'); Java.isFX = Java.mode === 'FX'; // this one could be merged into the previous Java.initApp = function(){ if(this.initialized){ return; } console.log("started Java.initApp..."); this.initialized = true; // further app initialisation } // One can mock this initApp call with jQuery.ready $(document).ready(function(){ Java.initApp(); }); Java: webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Worker.State t, Worker.State t1) { logger.info("[JAVA INIT] setting..."); if (t1 == Worker.State.SUCCEEDED) { logger.info("ok"); JSObject window = (JSObject) webEngine.executeScript("window"); window.setMember("bearFX", bearFX); window.setMember("Bindings", bindings); logger.info("[JAVA INIT] calling bindings JS initializer..."); webEngine.executeScript("Java.init(window);"); logger.info("[JAVA INIT] calling app JS initializer..."); webEngine.executeScript("Java.initApp();"); } } });
Source is available at Github
BearFX.java
Update for Kostya:
I'm being blocked by my russian censorship firewall on blogspot.ru (and I'm editing at blogspot.com), so I'll answer here - not, I haven't tried that.
Update for Kostya:
I'm being blocked by my russian censorship firewall on blogspot.ru (and I'm editing at blogspot.com), so I'll answer here - not, I haven't tried that.