Search code examples
javajavascriptjavafxrequirejsamd

JavaFX + Javascript : How to inject a Java object to an AMD module


While experimenting JavaFX + HTML5/JS, and I've come across AMD modules (RequireJS).

I know how to inject Java objects and use them within the "old-fashioned" Javascript (everything in the global scope), but I'm now wondering how I to inject a Java object in a RequireJS module.

Any advice?


Update 1

As I feel that I didn't make myself clear enough, let me be more specific.

My goal is to display my Javascript logs both on the browser console and the Java console because I don't want to include firebug-lite.j. To do that (in a simplified way):

  1. Java:: create a DummyLogger class

.

public class DummyLogger {
    void log(String msg) {
       System.out.println("LOG (JS) : " + msg); 
    }
}
  1. Java:: add a DummyLogger instance to the Javascript window object

.

WebView webView = new WebView()
webView.getEngine().load("index.html");
// Inject our Java logger interface to JS
final JSObject jsWnd = (JSObject) webView.getEngine().executeScript("window");
jsWnd.setMember("javaLogger", logger);
  1. Javascript:: In my module "module/logger.js", expose a log function that uses both the JS console and the javaLogger if it is defined

.

define(function() {
    return {
        log: function(message) {
            console.log(message);

            // Forward to the injected Java logger only if it is defined
            if(typeof javaLogger !== 'undefined') {
                javaLogger.log(message);
            }
        }
    }
}

Now, I'm wondering whether or not it is possible to inject the javaLogger object to the logger module without polluting the window namespace (which is kindda the point of AMD...).

My idea was to do something like:

  • add a function setJavaLogger(logger) on the JS logger module.
  • use that setter to inject the javaLogger directly to the logger module

.

WebView webView = new WebView()
WebEngine engine = webView.getEngine();

engine.getLoadWorker().stateProperty().addListener(
    (observable, oldState, newState) -> {
        if (newState == Worker.State.SUCCEEDED) {
            final JSObject retVal = (JSObject) engine.executeScript(
                "require(['module/logger'], function(logger) { return logger; });"
            );
            retVal.call("setJavaLogger", logger);
        }
    }
);

engine.load("index.html");

Unfortunately, this does nothing. Does the require function return something?

Is it even possible to retrieve my logger module in Java?


Solution

  • From Java, it is possible to access a RequireJS module with something like:

    WebView webView = new WebView();
    WebEngine engine = webView.getEngine();
    engine.load("path/to/index.html");
    
    // Assuming that somewhere in 'index.html', the loading of "module/logger" is triggered...
    
    { ... let some time pass ...} // 
    
    final JSObject loggerModule = (JSObject) engine.executeScript(
        "require('module/logger')"
    );
    logger.call("trace", "hello world!");
    

    WARNING: The loading of both the HTML page and the RequireJS modules are asynchronous!

    The execution of require(String) might fail and throw a netscape.javascript.JSException because the 'module/logger' is not yet loaded. Such exception shall look like:

    netscape.javascript.JSException: Error: Module name "module/logger" has not been loaded yet for context: _. Use require([])
    http://requirejs.org/docs/errors.html#notloaded
        at com.sun.webkit.dom.JSObject.fwkMakeException(JSObject.java:128)
        at com.sun.webkit.WebPage.twkExecuteScript(Native Method)
        at com.sun.webkit.WebPage.executeScript(WebPage.java:1426)
        at javafx.scene.web.WebEngine.executeScript(WebEngine.java:948)
        many more...
    

    You will have to find a more or less creative way to wait until your module is loaded.