iBreed: Hybrid application framework for JavaFX/HTML – Part I


iBreed: Part I

A new episode on my JavaFX UI series articles.
For the application I’m currently developing, I’d like to have a nice window decoration so I worked first on the “Undecorator” series. Now, I need to mix HTML5 content in my JavaFX app so I decided to create a light framework to facilitate this hybrid ecosystem.

So, please welcome: iBreed!

iBreed is a light framework for hybrid JavaFX/HTML5 applications.

Why hybrid?

To use the best of both worlds! Leverage the JavaFX platform (desktop integration, file system, back-end, Frameworks, gestures…) mixed with the HTML5 “universal” content.

The goal of iBreed

Mixing technologies is not a recommended approach. Now days, things are going fast and reuse instead of rewriting could be a tactical and interesting step.
And with JavaFX, this is possible ☺!
More than a mixing framework, it should be “fused”. Combining JavaFX and HTML technologies means bringing the same experience to the end-user: same look and feel, same fonts, unique drag and drop… i.e. the user interface must look uniform, no visual difference between HTML and JavaFX parts (e.g. no URL loading progress bar ☺!).

At current stage, here is what iBreed could bring to you:
1. A ready to run executable JAR to quickly test your HTML pages in a JavaFX context.
2. A WebView “injector” class with all implemented handlers (dialogs, Java Script bridge…)
3. A polished windowing system for hybrid applications.
4. A test HTML page with examples of interoperability (dnd, JS 2 Java…)

Note: Since I based the windows look on a flat style, I incorporated JMetro style as the root CSS of all Scenes (BTW, great work from Pixel Duke http://pixelduke.wordpress.com, available in JFXtras 2).

Try it out!

To Hybrid your App, take a look at the IBreedSkeleton class (https://github.com/in-sideFX/iBreed/blob/master/src/demoapp/IBreedSkeleton.java) that contains a minimal implementation. This “skeleton” provides an “undecorated” Stage with a customized WebView (WebViewInjector) fused with a basic JavaFX pane. Of course, include the iBreedDemo.jar (or get Undecorator.jar + iBreed.jar from repo) into your project.

public class IBreedSkeleton extends Application {

    @FXML
    private WebView webView;
    @FXML
    private TextField urlTxt;

    UndecoratorScene undecoratorScene;

    // The bridge object
    JavaScriptBridge javaScriptBridge;

    @Override
    public void start(final Stage stage) throws Exception {
        Pane root = null;
        // UI part of the decoration
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ibreedskeleton.fxml"));
            fxmlLoader.setController(this);
            root = (Pane) fxmlLoader.load();
        } catch (Exception ex) {
            System.err.println(ex);
        }

        // Add Chrome
        undecoratorScene = new UndecoratorScene(stage, root);
        undecoratorScene.setFadeInTransition();
        setAsHybrid(stage);
        stage.setTitle("iBreed");
        stage.setScene(undecoratorScene);
        stage.sizeToScene();
        stage.toFront();
        stage.centerOnScreen();

        /**
         * Fade transition on window closing request
         */
        stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent t) {
                t.consume();    // Do not hide
                undecoratorScene.setFadeOutTransition();
            }
        });

        // Set stage's sizes based on client area's sizes
        Undecorator undecorator = undecoratorScene.getUndecorator();
        stage.setMinWidth(undecorator.getMinWidth());
        stage.setMinHeight(undecorator.getMinHeight());

        stage.setWidth(undecorator.getPrefWidth());
        stage.setHeight(undecorator.getPrefHeight());
        if (undecorator.getMaxWidth() > 0) {
            stage.setMaxWidth(undecorator.getMaxWidth());
        }
        if (undecorator.getMaxHeight() > 0) {
            stage.setMaxHeight(undecorator.getMaxHeight());
        }
        stage.show();
    }

    /**
     * Customize the user interface for hybrid application
     *
     * @param stage
     */
    public void setAsHybrid(Stage stage) {
        // The generic object for JS and JavaFX interop
        javaScriptBridge = new JavaScriptBridge(webView.getEngine());
        javaScriptBridge.fromJSProperty.addListener(new ChangeListener<String>() {
            /**
             * Invoked when JavaScript sends a new message
             */
            @Override
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
                System.out.println("Something changed on Java Script side...");
            }
        });

        // Inject WebView customizations (handlers...)
        WebViewInjector.inject(stage, webView, javaScriptBridge);

        // Default URL to load
        final String url = getClass().getResource("page_skeleton.html").toExternalForm();
        webView.getEngine().load(url);

        // Reflect the current URL in the text field
        webView.getEngine().getLoadWorker().stateProperty().addListener(
                new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> ov,
                    Worker.State oldState, Worker.State newState) {
                if (newState == Worker.State.SUCCEEDED) {
                    urlTxt.setText(webView.getEngine().getLocation());
                    urlTxt.setStyle("-fx-background-color:white;;-fx-border-color: #bababa;-fx-border-width: 2px;-fx-border-style: solid;");
                    // urlTxt.setStyle("-fx-background-color:white;");
                } else if (newState == Worker.State.FAILED) {
                    urlTxt.setStyle("-fx-background-color:red;");
                }
            }
        });
    }

    /**
     * If user enters new url
     *
     * @param event
     */
    @FXML
    private void onUrlTxtChanged(ActionEvent event) {
        String url = urlTxt.getText();
        webView.getEngine().load(url);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Visual result is:

iBreed skeleton window

iBreed skeleton window

IBreed demo

I also delivered a more complete example as a test “suite” for my hybrid application.

You can execute it by clicking on this icon:  macJar

This sample covers:
• The 3 main popup types (prompt, confirm, alert)
• The external “browser” window popup type
• Basic drag and drop
• The two ways communication (Java to JS and JS to Java).
And it looks like:

iBreed Stage

iBreed Stage

Video:

Takeaway

All sources and binaries are here: https://github.com/in-sideFX/iBreed/blob/master/

What’s next?

I’ll enrich iBreed by:
• Providing a consistent Look and feel (CSS) across the app: Stage, JavaFX Node’s skin, HTML CSS.
• Delivering more helpers (dnd…)
• Adding fancy effects for html page loading
• Fixing some “bugs” and performance issue on Windows using Java 8 (big fonts, Stage resize is slow…)

Thanks for reading and feel free to comment!

  1. #1 by Mircea on 20/04/2013 - 08:48

    That’s really nice. Good job.

  2. #2 by move on 01/05/2013 - 03:05

    Keep up the very good work.

  3. #3 by Makis Teller on 19/07/2013 - 00:44

    Excelent work, I will read your code trying to make it even better, you are inspiring me.
    Continue this work and thanks you!

    • #4 by arnaud nouard on 19/07/2013 - 13:48

      Thanks a lot, happy to read that !
      May the best Developer win 🙂 !
      A.

  1. Java desktop links of the week, April 22 | Jonathan Giles
  2. JavaFX links of the week, April 22 // JavaFX News, Demos and Insight // FX Experience
  3. aLive is alive! | In-SideFX

Leave a comment