© Stephen Chin, Johan Vos and James Weaver 2019
S. Chin et al.The Definitive Guide to Modern Java Clients with JavaFXhttps://doi.org/10.1007/978-1-4842-4926-0_9

9. JavaFX, the Web, and Cloud Infrastructure

Stephen Chin1 , Johan Vos2 and James Weaver3
(1)
Belmont, CA, USA
(2)
Leuven, Belgium
(3)
Marion, IN, USA
 

Written by Anton Epple, Johan Vos, Bruno Borges, and José Pereda

Client applications, as the term itself already hints, are rarely self-contained. In order to function properly, they need to access other components, for example, server applications. JavaFX, being a Java framework, allows application developers to leverage the wide Java ecosystem. Libraries created for interacting with, for example, REST APIs, web components, SOAP endpoints, and encryption APIs can be used in JavaFX applications. As such, there is nothing special about a JavaFX application vs. a non-JavaFX application. However, there are a number of features in JavaFX that enable developers to create an easy, secure, reliable connection to other (backend or server-side) components.

In this chapter, we will discuss two approaches for integrating backend components with JavaFX applications. First, we’ll discuss the WebView component. At its core, the JavaFX WebView control has most of the functionalities of a web browser, and it allows for two-way interaction between script elements on the web pages and Java functions in the JavaFX application. It is often used as an easy way to render existing functionality from a web site into a desktop application.

This approach is shown in Figure 9-1.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig1_HTML.jpg
Figure 9-1

Integrating backend functionality using WebView

Next, a more flexible and general approach is discussed for connecting JavaFX controls to remote endpoints in cloud or backend infrastructure. In this approach, the JavaFX application communicates directly with the backend APIs, as shown in Figure 9-2.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig2_HTML.jpg
Figure 9-2

The JavaFX application communicates directly with the backend APIs

Integrating with the Web

With the WebView component, you can display HTML5 content in your JavaFX application. This works for a local HTML file or a web page. The component is based on WebKit and is quite powerful. The possible applications range from the display of documentation to the integration of complete web applications. What is very practical is that it is easy to interact with the content of the WebView component via a JavaScript bridge. This allows us to modify the page from JavaFX and react to user actions. With the help of the open source project “DukeScript,” you can even bind HTML elements directly to JavaFX properties.

Displaying a Web Page

The WebView API itself is very easy to use for simple use cases. So we can start with a code example right away. The core component is the WebView Node. We can add it to the SceneGraph like any other node. To load its WebEngine and pass a URL to it,
public class WebViewDemo extends Application {
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        engine.load("https://openjfx.io");
        Scene scene = new Scene(webView, 300, 250);
        primaryStage.setTitle("JavaFX WebView Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig3_HTML.jpg
Figure 9-3

Loading a web site in JavaFX WebView

The three highlighted lines of code suffice to load the web page as shown in Figure 9-3. With a right-click, you can open a context menu on the page and, depending on the current status, stop or restart the loading of the page. This menu is available by default. For the most common use cases, you won’t need it. Let’s disable it with the following command:
webView.setContextMenuEnabled(false);

Adding Navigation and History

To use the WebEngine’s load command for navigating between web sites, you can add buttons or menus to your JavaFX application to display a specific page when clicked. In response to a click, just call engine.load("http://myurl.com") to instruct the WebEngine to load the page for you. Let’s try this in our example with a MenuBar as shown in Figure 9-4. To do this, we first put the WebView into a BorderPane so we can easily insert a MenuBar above it. Change the start method like this:
public void start(Stage primaryStage) {
        WebView webView = new WebView();
        webView.setContextMenuEnabled(false);
        WebEngine engine = webView.getEngine();
        engine.load("https://openjfx.io ");
        BorderPane borderPane= new BorderPane(webView);
        MenuBar menuBar = new MenuBar();
        final Menu navigateMenu = new Menu("Navigate");
        MenuItem home = new MenuItem("Home");
        navigateMenu.getItems().addAll(home);
        home.setOnAction(e -> engine.load("http://dukescript.com "));
        menuBar.getMenus().add(navigateMenu);
        borderPane.setTop(menuBar);
        Scene scene = new Scene(borderPane, 640, 400);
        primaryStage.setTitle("JavaFX WebView Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig4_HTML.jpg
Figure 9-4

Adding a MenuBar for navigation

The WebEngine also makes the navigation history available to us. The WebEngine’s getHistory method returns a History object that has a getEntries method. That’s an ObservableList we can use to keep track of changes. Let’s use that to fill a menu:
Menu historyMenu = new Menu("History");
        engine.getHistory().getEntries().addListener((ListChangeListener.Change<? extends Entry> c) -> {
            c.next();
            for (Entry e: c.getAddedSubList()) {
                for(MenuItem i: historyMenu.getItems()){
                    if (i.getId().equals(e.getUrl())){
                        historyMenu.getItems().remove(i);
                    }
                }
            }
            for (Entry e: c.getAddedSubList()) {
                final MenuItem menuItem = new MenuItem(e.getUrl());
                menuItem.setId(e.getUrl());
                menuItem.setOnAction(a->engine.load(e.getUrl()));
                historyMenu.getItems().add(menuItem);
            }
        });
        menuBar.getMenus().addAll(navigateMenu, historyMenu);
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig5_HTML.jpg
Figure 9-5

Accessing browsing history

The result is shown in Figure 9-5. That’s not much effort for building a nice basic browser. Now it’s time to improve the user experience. Currently, we get no indication of the loading process.

Showing Loading Progress

A progress bar would be very helpful here. We can display it at the bottom of the page while loading new content. Fortunately, the WebEngine is using the javafx concurrency APIs, so we can get a Property to easily track progress. So let's add a ProgressBar to our BorderPane to show the progress:
        ProgressBar progressBar = new ProgressBar();
        progressBar.progressProperty().bind(
                engine.getLoadWorker().progressProperty());     progressBar.visibleProperty().bind(engine.getLoadWorker().stateProperty().isEqualTo(State.RUNNING));
        borderPane.setBottom(progressBar);
We also bind the visibleProperty of our ProgressBar to the stateProperty of the Worker. A very small code sample illustrates perfectly how well properties and bindings are integrated with the rest of the JavaFX APIs. There’s no need for listeners, which really helps to improve the readability of our code. The example with a working progress bar is shown in Figure 9-6.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig6_HTML.jpg
Figure 9-6

Showing progress while loading a page

Executing JavaScript

The fact that you can display HTML5 web pages in the application is a great thing if you come from a Swing background. But we can do much more with it. For example, we can call JavaScript commands from Java. That doesn't sound particularly exciting at first, especially if you're dealing with a normal web site. But it becomes really interesting when we reuse one of the numerous JavaScript libraries. Many of them contain components that could also serve us well in a JavaFX application. For the following example, we use the highcharts library and jQuery. You can download this library from www.highcharts.com/ and jQuery from http://jquery.com. Now create a new JavaFX project and load a local HTML file from the JAR archive in the Application class:
public class WebViewCharts extends Application {
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        final WebEngine engine = webView.getEngine();
        engine.load(WebViewCharts.class.getResource("charts.html").toExternalForm());
        Scene scene = new Scene(webView, 300, 250);
primaryStage.setTitle("JavaFX WebView Chart Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
To get the URL of the HTML page, we can call getClass().getResource(“charts.html”). For this, the file needs to be in the same package as the main class. Now create the file charts.html with the following content:
<!DOCTYPE html>
<html>
    <head>
        <title>An additional Charts Library</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script type="text/javascript" src="jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="highcharts.js"></script>
    </head>
    <body>
        <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
    </body>
</html>
The two javascript files for highcharts and jQuery should also be placed as resources in the same package as the WebView tries to find them relative to the page. If you want to use a different version of Highcharts or jQuery, you will need to customize the corresponding links in the preceding HTML file. HighCharts is a chart library that we can use to create graphs that are not yet supported by JavaFX. For our example, we’re building a rudimentary Java API that helps us build the JavaScript code we need. For reasons of readability, I deliberately keep the API simple here. But we will at least store the data series in a Java object. Now let’s create the Series class:
class Series {
    private final String title;
    private final float[] seriesData;
    public Series(String title, float[] seriesData) {
        this.title = title;
        this.seriesData = seriesData;
    }
    public String getTitle() {
        return title;
    }
    public String getSeriesDataAsString(){
        return Arrays.toString(seriesData);
    }
}
To execute our JavaScript code, we need to wait until the page is loaded. To monitor loading state, we can use the same Property we used for displaying the ProgressBar. Let’s register a listener to the state of the LoadWorker:
        engine.getLoadWorker().stateProperty().addListener((e, o, n) -> {
            if (n == State.SUCCEEDED) {
                String js = constructJavaScript("Some made up data", new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}, "Temperature",
                        new Series("New York", new float[]{1.0f, 6.9f, 9.5f, 14.5f, 19.2f, 17.5f, 25.2f, 26.5f, 23.3f, 18.3f, 13.9f, 9.6f}),
                        new Series("Tokyo", new float[]{-0.2f, 0.8f, 9.7f, 11.3f, 17.0f, 22.0f, 24.8f, 24.1f, 20.1f, 14.1f, 8.6f, 2.5f}),
                        new Series("Paris", new float[]{3.9f, 3.2f, 5.7f, 8.5f, 11.9f, 15.2f, 17.0f, 16.6f, 14.2f, 10.3f, 6.6f, 4.8f}),
                new Series("Rome", new float[]{7,8f, 4.2f, 5.7f, 8.5f, 11.9f, 15.2f, 17.0f, 16.6f, 14.2f, 10.3f, 6.6f, 4.8f})
                );
                System.out.println(js);
                engine.executeScript(js);
            }
        });

So far, the code still looks OK; the dirty secrets are hidden in the method constructJavaScript. This and another helper method for converting a List to a comma-separated String are added now:

public String constructJavaScript(String title, String[] categories, String xAxis, Series... series) {
        String result = "$(function () { "
                + "        $('#container').highcharts({ "
                + "            title: { "
                + "                text: '" + title + "', "
                + "                x: -20 //center "
                + "            }, "
                + "            xAxis: { "
                + "                categories: "
                + convertToCommaDelimited(categories)
                + " "
                + "            }, "
                + "            yAxis: { "
                + "                title: { "
                + "                    text: '" + xAxis + "' "
                + "                }, "
                + "                plotLines: [{ "
                + "                    value: 0, "
                + "                    width: 1, "
                + "                    color: '#808080' "
                + "                }] "
                + "            }, "
                + "            tooltip: { "
                + "                valueSuffix: '°C' "
                + "            }, "
                + "            legend: { "
                + "                layout: 'vertical', "
                + "                align: 'right', "
                + "                verticalAlign: 'middle', "
                + "                borderWidth: 0 "
                + "            }, "
                + "            series: [{ ";
        for (Series serie : series) {
            result += "                name: '" + serie.getTitle() + "', "
                    + "                data: " + serie.getSeriesDataAsString() + " "
                    + "            }, { ";
        }
        result = result.substring(0, result.lastIndexOf(','));
        result
                += "] "
                + "        }); "
                + "    }); "
                + "     "
                + " "
                + " "
                + "";
        return result;
    }
    public static String convertToCommaDelimited(String[] list) {
        StringBuilder ret = new StringBuilder("[");
        for (int i = 0; list != null && i < list.length; i++) {
            ret.append("'");
            ret.append(list[i]);
            ret.append("'");
            if (i < list.length - 1) {
                ret.append(',');
            }
        }
        ret.append(']');
        return ret.toString();
    }
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig7_HTML.jpg
Figure 9-7

Using JavaScript chart libraries in JavaFX

That’s a bit ugly, isn’t it? We construct a JavaScript String from our data. This part of the work isn't really much fun and is difficult to debug. But once you have built a proper Java API for the call, you will be rewarded with new JavaFX components that provide new functions. With executeScript, we can now execute it, and our data is displayed as shown in Figure 9-7. The advantage of this approach is that practically everything can be implemented that works in a web application, as long as the WebView supports it. JavaScript code can even be shared between web applications and desktop applications of a company.

Registering Java Callbacks

So far, we’ve used the WebView to show static content and used the JavaScript bridge to dynamically generate content and display it. But to make it really interactive, we need to be able to react in Java to events from inside the HTML page. In fact, with the WebView, we can also call Java functions from JavaScript.

Let’s build a small example. First, we create a small form in HTML. In this form, we create a field in which the user can enter his name. Later, we want to call a Java method from JavaScript that outputs this name:
<!DOCTYPE html>
<html>
    <body>
        <form>
            Last name: <input id="name" type="text" name="name">
            <input id="clickMe" type="button" value="clickme"
                   onclick="val = document.getElementById('name').value; formValues.print(val);"
                   />
        </form>
    </body>
</html>
Now we create the Java class ValuePrinter, whose print method is to be called from JavaScript:
public class ValuePrinter {
    public  void print(String name){
        System.out.println("value "+name);
    }
}
In our application class, we register an instance of it to the WebEngine as a member:
public class WebViewForms extends Application {
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        final WebEngine engine = webView.getEngine();
        engine.load(WebViewForms.class.getResource("form.html").toExternalForm());
        engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
                JSObject script = (JSObject) engine.executeScript("window");
                script.setMember("formValues", new ValuePrinter());
            }
        });
        Scene scene = new Scene(webView, 600, 250);
        primaryStage.setTitle( "JavaFX WebView Callback Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

When you start the application, clicking the HTML button will access this Java object via JavaScript, and the print method will pass the content of the form field as a String.

So the WebView not only provides you with a way to integrate HTML5 content in your JavaFX application; it also enables you to synchronize contents via two-way communication. This has successfully been used in many companies to provide a powerful bridge between web and desktop applications. But the process is tedious and leads to solutions which are hard to debug and difficult to maintain. In the next part, we’re looking at some open source projects which make these tasks much simpler.

FXML and the Model View ViewModel Pattern

Admittedly, creating a Java API for a JavaScript library like this is an ugly task. DukeScript (http://dukescript.com) is a framework which makes this task a lot easier. While DukeScript is a full-featured Java UI framework on its own, we will only focus on how we can use it to save us from the pains of synchronizing between Java and JavaScript in JavaFX. Fortunately, its core features are an API for painless Java and JavaScript interaction and an API for direct binding of HTML elements and attributes to Java properties.

DukeScript allows you to use HTML views in the same way you use FXML. FXML is, just like HTML, a declarative markup language for designing user interfaces. The view elements and their properties can be bound to view logic in a controller (or viewmodel) class. Let’s recap how you load a view with FXML:
Node root = FXMLLoader.load(MyViewModel.class.getResource("view.fxml"));
You can bind events to viewmodel methods annotated with the @FXML annotation, and there's some support binding properties of your widgets to viewmodel properties. This way, you can declaratively set up synchronization between the view (FXML) and the viewmodel (controller). For example, this is FXML code sample you get, when you create a “JavaFX FXML Application” in NetBeans:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication3.FXMLDocumentController">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
    </children>
</AnchorPane>
The onAction attribute binds a Click to the handleButtonAction method in the controller. Let’s have a look at the controller code:
public class FXMLDocumentController implements Initializable {
    @FXML
    private Label label;
    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }
}
Now we’re ready to show our view and test it. Let’s create a demo application with a tab pane, so we can later add a WebView-based clone of it and see them side by side:
public class MainApp extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        TabPane tabPane = new TabPane();
        Parent parent = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
        tabPane.getTabs().add(new Tab("FXML", parent));
        Scene scene = new Scene(tabPane);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setTitle("JavaFX and DukeScript");
        stage.setScene(scene);
        stage.show();
    }
    // optional main method with launch code omited...
}
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig8_HTML.jpg
Figure 9-8

A simple FXML demo

The resulting tabbed UI is shown in Figure 9-8. This is a very popular pattern in UI development also known as Model-View-ViewModel (or MVVM) . The FXMLLoader uses the @FXML annotation for exposing functions and relies on reflection for binding user actions to method calls and View properties to properties in the ViewModel.

FXML unfortunately doesn’t fully implement MVVM. Its expression language lacks declarative bindings for loops and control constructs like if or else. Therefore, you need to use Java in many places to manipulate the Nodes directly. To do this, you can also inject Nodes into the viewmodel using the @FXML annotation, which breaks the isolation required by MVVM. But still FXML is useful, as it supports at least some declarativeness.

Writing a Controller for HTML

Now let’s recreate this simple example with the WebView and HTML. We’ll need to add some external libraries. The simplest way is by using Maven or Gradle. These are the dependencies you’ll need:
      <dependency>
            <groupId>org.netbeans.html</groupId>
            <artifactId>net.java.html</artifactId>
            <version>1.6<version>
        </dependency>
        <dependency>
            <groupId>org.netbeans.html</groupId>
            <artifactId>net.java.html.json</artifactId>
            <version>1.6</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.netbeans.html</groupId>
            <artifactId>ko4j</artifactId>
            <version>1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.netbeans.html</groupId>
            <artifactId>net.java.html.boot.fx</artifactId>
            <version>1.6</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.dukescript.api</groupId>
            <artifactId>javafx.beaninfo</artifactId>
            <version>0.3</version>
        </dependency>
All of the above libraries can be used for free under the Apache License, version 2, and under the MIT License. We’ll start with a simple JavaFX controller with a couple of properties and methods. While the FXMLLoader relies on reflection and annotations to set up the wiring between the view and its viewmodel, we’re using a class called FXBeanInfo, which gives direct access to the properties exposed by the viewmodel. The benefit of this is that there’s no reflection required, which makes the system faster and easier to test. To expose properties, you’ll implement a single method interface, FXBeanInfo.Provider:
class HTMLController implements FXBeanInfo.Provider {
    private final StringProperty labelText = new SimpleStringProperty(this, "labelText", "");
    private final FXBeanInfo info = FXBeanInfo
            .newBuilder(this)
            .property(labelText)
            .build();
    @Override
    public FXBeanInfo getFXBeanInfo() {
        return info;
    }
}

FXBeanInfo supports the builder pattern for easily creating the FXBeanInfo using the actual properties of your bean. You’re not required to write getters and setters for your properties, since they can be accessed using the FXBeanInfo. The consumer of the FXBeanInfo can simply call the method getProperties in order to get a map of the available properties. It’s a map instead of a set or list, so you can also easily access a property by its name.

It’s easy to see how using this could also improve the way the FXMLLoader currently works. Instead of scanning the controller via reflection for getters and setters of a bound property, the loader could simply access the Property via the FXBeanInfo. No reflection is required at all – clean, fast, and standardized access to all exposed properties.

In order to make actions easily accessible as well, action handlers are also stored in properties. This way they are just as easily accessible by name as the regular properties:
class HTMLController implements FXBeanInfo.Provider {
    private final StringProperty labelText = new SimpleStringProperty(this, "labelText", "");
    private final Property<EventHandler<ActionEvent>> action =
            new SimpleObjectProperty<EventHandler<ActionEvent>>(
                    this, "action",
                    (e) -> labelText.set("Hello World!"));
    private final FXBeanInfo info = FXBeanInfo
            .newBuilder(this)
            .action(action)
            .property(labelText)
            .build();
    @Override
    public FXBeanInfo getFXBeanInfo() {
        return info;
    }
}
So later, when an HTML button click is bound to this “action,” the system uses FXBeanInfo to retrieve the action handler via the map. In the same way, FXBeanInfo also helps us in writing unit tests. We can test the complete view logic without the need to instantiate the view. Let’s test our controller to make sure it works as expected before we create the view. In our simple example, we can only test if the action does what it’s expected to do – change the label text from an empty String to “Hello Word!” So we’ll use FXBeanInfo to get the property holding the label text and the action handler. Then we assert the string is empty, invoke the action handler, and check the label text again:
public class HTMLControllerTest {
    @Test
    public void hello() {
        HTMLController controller = new HTMLController();
        FXBeanInfo fxBeanInfo = controller.getFXBeanInfo();
        ObservableValue<?> labelText = fxBeanInfo.getProperties().get("labelText");
        assertEquals("", labelText.getValue());
        EventHandler<? super ActionDataEvent> action =
                fxBeanInfo.getActions().get("action").getValue();
        action.handle(null);
        assertEquals("Hello World!", labelText.getValue());
    }
}

FXML controllers are much harder to test in comparison, as you need to somehow inject the @FXML objects and initialize the JavaFX platform in order to test the logic or use an additional testing framework.

Adding the HTML View

The view is defined in regular HTML. And like in FXML, attributes are used to bind events to actions, and properties of HTML elements can be bound to viewmodel properties. I’ll skip the CSS:
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <style>
            html, body {
                height: 100%;
            }
            body {
                margin: 0;
            }
            .flex-container {
                height: 100%;
                padding: 0;
                margin: 0;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .row {
                width: auto;
            }
            .flex-item {
                padding: 5px;
                margin: 10px;
                line-height: 20px;
                font-weight: bold;
                font-size: 2em;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div class="flex-container">
            <div class="row">
                <div class="flex-item"><span data-bind="text: labelText"></span></div>
                <div class="flex-item"><button data-bind="click: action">Click me!</button></div>
            </div>
        </div>
    </body>
</html>

The bindings are defined in data-bind attributes and follow a simple pattern. First, there’s the name of the binding, “text” and “click” in our example, followed by the bound property or action, in this example “labelText” and “action.” There are a lot of bindings supported for the different user interactions and controls, and there is support for loops and conditional statements. This makes it possible to declare all bindings completely in the view. Unlike with FXML, there’s no need to inject Nodes to the controller. This way the controller stays unit testable and completely view independent.

Now to invoke the view and set up the bindings, there’s a little bit of boilerplate code. If you have more than one HTML-based view, it’s better to hide that in an HTMLLoader which can be used similar to the FXMLLoader. Here’s this little helper class:
public class HTMLLoader {
    public static WebView load(URL html, final FXBeanInfo.Provider viewModel){
        WebView webView = new WebView();
        FXBrowsers.load(webView, html, new Runnable() {
            @Override
            public void run() {
                Models.applyBindings(viewModel);
            }
        });
        return webView;
    }
}
So finally we’re ready to add our HTML view, and after that run the demo. Add this to our demo application and run it:
WebView webview = HTMLLoader.load(getClass().getResource("/html/view.html"), new HTMLController());
tabPane.getTabs().add(new Tab("HTML",webview));
The UI updated to display an HTML tab is shown in Figure 9-9.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig9_HTML.jpg
Figure 9-9

FXML and HTML demo running side by side

HTML Data Binding Explained

As mentioned before, this library has a more complete set of bindings. It includes loops and conditionals. We’ll now use a slightly more complex controller to demonstrate this:
public class TodoListHTMLController implements FXBeanInfo.Provider {
    final ObjectProperty<String> input = new SimpleObjectProperty<>(this, "input");
    final ObjectProperty<TodoElement> selected = new SimpleObjectProperty<>(this, "selected");
    final ListProperty<TodoElement> todos = new SimpleListProperty<>(this, "todos", FXCollections.observableArrayList());
    final Property<EventHandler<Event>> add = new SimpleObjectProperty<>(this, "add");
    final Property<EventHandler<ActionDataEvent>> remove = new SimpleObjectProperty<>(this, "remove");
    final FXBeanInfo info = FXBeanInfo.newBuilder(this).
            property(input).
            property(selected).
            property(todos).
            action(remove).
            action(add).
            build();
    public TodoListHTMLController() {
        todos.add(new TodoElement("Buy milk!"));
        add.setValue(e -> todos.add(new TodoElement(input.get())));
        remove.setValue((event) -> {
            TodoElement toRemove = event.getSource(TodoElement.class);
            todos.get().remove(toRemove);
        }
        );
    }
    @Override
    public FXBeanInfo getFXBeanInfo() {
        return info;
    }
    private static final class TodoElement implements FXBeanInfo.Provider {
        final String message;
        final FXBeanInfo info;
        TodoElement(String message) {
            this.message = message;
            this.info = FXBeanInfo.newBuilder(this).
                    constant("message", message).
                    build();
        }
        @Override
        public FXBeanInfo getFXBeanInfo() {
            return info;
        }
    }
}
This TodoListHTMLController has List-type properties and the List Entries are of type TodoElement, which is a nested viewmodel. The remove action is slightly different from the actions we’ve seen before, as it can handle an ActionDataEvent. Let’s investigate why this is necessary after we look at the HTML:
<html>
    <head>
        <title>Todo List</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <input data-bind="textInput: input" />
        <button data-bind="click: add">add</button><br>
         <ul data-bind="foreach: todos">
            <li>
                <span data-bind="text: message"></span>
                (<a href="#" data-bind="click: $root.remove">remove</a>)
            </li>
        </ul>
    </body>
</html>

You can see how the input Property of our controller is bound to the input element via the “textInput” binding. Whenever the user enters some text, the corresponding Property will update automatically. The “add” button is bound to the add action via the “click” binding. That’s similar to the first example. Next, the UL element is bound to the “todos” Property via the “foreach” binding. This means that everything inside this element, the List Item Element, will be treated as a template and copied for each entry. After it’s copied, the TodoElement will be set as the context object for this part of the HTML, and the bindings will be applied.

The text of the span inside is bound to the “message” property of the TodoElement via the “text” binding. We also have a link here. This link is bound to the “remove” action of the root object, which is our TodoListHTMLController. When the link is clicked, an ActionDataEvent will be created and passed to the EventHandler. The ActionDataEvent contains the TodoElement it was called from, so we can remove it from the list of todos.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig10_HTML.jpg
Figure 9-10

Using foreach bindings and ActionDataEvent

The demo in Figure 9-10 doesn’t look very nice, but since this is plain HTML and CSS, there are millions of web designers and developers out there to help you with the styling. Figure 9-11 shows how the demo looks with some styling. You’ll find the HTML and CSS in the demo repository.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig11_HTML.jpg
Figure 9-11

Our sample with some CSS styling applied

With FXBeanInfo, HTML is easier to use than FXML. There’s no need for reflection, which makes the system fast and simple. With no need to inject Nodes into the viewmodel, the view logic is completely unit testable. To learn more about all the available bindings, check out https://dukescript.com/knockout4j.html#ko-bindings.

Displaying a Map with DukeScript

Besides offering an easy way to bind HTML elements to JavaFX properties, the DukeScript project has created a lot of libraries you can directly use in your application. For example, there’s Leaflet4j (https://github.com/dukescript/leaflet4j), a JavaFX map component based on the JavaScript Leaflet component. This is how you create a map with the Java API:
MapOptions mapOptions = new MapOptions()
        .setCenter(new LatLng(48.336614, 14.319305))
        .setZoom(15);
final Map map = new Map("map", mapOptions);
// add a tile layer to the map
TileLayerOptions tlo = new TileLayerOptions();
tlo.setAttribution("Map data &copy; <a href='http://www.thunderforest.com/opencyclemap/'>OpenCycleMap</a> contributors, "
        + "<a href='http://creativecommons.org/licenses/by-sa/2.0/'>CC-BY-SA</a>, "
        + "Imagery © <a href='http://www.thunderforest.com/'>Thunderforest</a>");
tlo.setMaxZoom(18);
TileLayer layer = new TileLayer("http://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png", tlo);
map.addLayer(layer);
// Add a polygon. When you click on the polygon a popup shows up
Polygon polygonLayer = new Polygon(new LatLng[] {
        new LatLng(48.335067, 14.320660),
        new LatLng(48.337335, 14.323642),
        new LatLng(48.335238, 14.328942),
        new LatLng(48.333883, 14.327612)
});
polygonLayer.addMouseListener(MouseEvent.Type.CLICK, new MouseListener() {
     @Override
    public void onEvent(MouseEvent ev) {
        PopupOptions popupOptions = new PopupOptions().setMaxWidth(400);
        Popup popup = new Popup(popupOptions);
        popup.setLatLng(ev.getLatLng());
        popup.setContent("The Leaflet API for Java has been created here!");
        popup.openOn(map);
    }
});
map.addLayer(polygonLayer);

This example is taken directly from the Javadoc (www.dukescript.com/javadoc/leaflet4j/). There’s no JavaScript required. The API works just like a regular JavaFX component, in case you need a nice map component for your JavaFX application.

There are many more libraries which have already been adapted for use with Java-based models, including a Canvas API (https://github.com/dukescript/dukescript-canvas) and a Charts API (https://dukescript.com/javadoc/charts/) which internally uses the HighCharts library we used in our earlier demo.

From Web Sites to APIs

In this section, we’ll show how existing web sites can easily be rendered in JavaFX applications. By linking JavaScript functionality on the web sites with Java functions in the JavaFX application, it is possible to enhance the web site functionality and make it more interactive or to integrate it with functionality that is not available to web browsers (e.g., connection with devices).

While this is often a quick and simple solution for creating a desktop application based on an existing web site, in most cases it doesn’t provide the rich functionality offered by the backend, and it doesn’t leverage the rich functionality offered by the JavaFX APIs.

In the second part of this chapter, we will discuss how to access backend functionality and integrate it with JavaFX controls in a more granular way.

Building for the Cloud

Around 2012, the cloud finally went mainstream, but only with very few players that still today dominate the market. And yet, the competition is huge, with services and APIs being announced every quarter. Building applications for the cloud usually means taking advantage of these highly available and scalable infrastructure as a service, as well as highly productive and easy to use and integrate platforms as a service. Whether it’s a virtual machine or a database that is quickly provisioned for you or a face detection API that just needs a subscription key, all these resources have one thing in common: it is not you who is managing, maintaining, and scaling them, but your cloud provider.

Cloud also often means building web-based applications or web-based REST APIs that will run in the cloud and talk to resources also living in the cloud. How these applications and services are built and deployed often falls in the microservices and cloud-native architecture playbook. Scalability patterns of cloud resources apply fairly well to these web applications and microservices. And when everything is on the Internet and applications are accessed through the browser, some automated HTTP-based client, or messaging systems, many challenges that are common to desktop applications such as version update, data caching, service rerouting, and so on simply do not exist or are much easier to be solved in the cloud.

So what happens when the user-facing application is a rich client? How can developers take advantage of the cloud, and why? The focus here is in providing value in their modern client desktop application by not having to develop certain algorithms and business logic within the client itself and instead either move them to a place where developers can have better control and deliver updates faster or consume ready-to-use service APIs that would otherwise had consumed a significant time of development to be built in the rich client.

Compared to the scenario where the client is simply displaying a WebView with a web site, this approach is much more powerful. Some parts of the functionality can be implemented in the client, while other parts can be offloaded to the cloud provider.

Architecture of a JavaFX Cloud Application

Most modern enterprise systems have a multitier architecture, in which the web-based frontend and the business logic are separated. Typical web frameworks query the business tier, either using direct (Java) calls or REST APIs.

Use Case: Querying OpenWeather

We will now show a JavaFX application that queries the OpenWeather API (https://openweathermap.org) to retrieve the weather for a given location. We could have simply used a WebView and render the existing OpenWeather web site, but we want to leverage the power of the JavaFX controls, or we want to integrate seamlessly the weather data within our existing JavaFX UI application.

We will now explain how to write an application that retrieves queries from OpenWeather. In order to do so, the first thing is to sign up in the portal (https://home.openweathermap.org/users/sign_up) and get the default API key or generate a new one (https://home.openweathermap.org/api_keys).

For starters, lets create a simple application that will display the weather at a given time and city:
public class WeatherApp extends Application {
    private static final String API_KEY = "XXXXXXXXXXX";
    private static final String CITY = "London";
    private ImageView imageView;
    private Label weatherLabel;
    private Label descriptionLabel;
    private Label tempLabel;
    @Override
    public void start(Stage stage) {
        imageView = new ImageView();
        imageView.setFitHeight(100);
        imageView.setPreserveRatio(true);
        imageView.setEffect(new DropShadow());
        Label label = new Label("The weather in " + CITY);
        weatherLabel = new Label();
        descriptionLabel = new Label();
        descriptionLabel.getStyleClass().add("desc");
        tempLabel = new Label();
        tempLabel.getStyleClass().add("temp");
        VBox root = new VBox(10,
            label, imageView, weatherLabel, descriptionLabel, tempLabel);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 600, 400);
        scene.getStylesheets().add(
            WeatherApp.class.getResource("/styles.css").toExternalForm());
        stage.setScene(scene);
        stage.setTitle("The Weather App");
        stage.show();
        retrieveWeather();
    }
    private void retrieveWeather() {
        // TODO
    }
}
And the CSS content:
.label {
    -fx-font-size: 1.4em;
    -fx-text-fill: blue;
}
.label.desc {
    -fx-font-size: 1.2em;
    -fx-text-fill: gray;
}
.label.temp {
    -fx-font-size: 1.1em;
    -fx-text-fill: green;
}
To query OpenWeather for a given city, with our API key, basically we just need to create this query:
"https://api.openweathermap.org/data/2.5/weather?appid="
    + API_KEY + "&q=" + CITY
And listen to the response, in the form of a JSON string, like:
    {"coord":{"lon":-0.13,"lat":51.51},
     "weather":[{"id":500,"main":"Rain",
     "description":"light rain","icon":"10d"}],"base":"stations",
     "main":{"temp":290.14,"pressure":1012,"humidity":68,
     "temp_min":288.15,"temp_max":292.59},"visibility":10000,
     "wind":{"speed":4.1,"deg":180},"clouds":{"all":40},
     "dt":1563527401,"sys":{"type":1,"id":1414,"message":0.0137,
     "country":"GB","sunrise":1563509115,"sunset":1563566876},
     "timezone":3600,"id":2643743,"name":"London","cod":200}
There are multiple ways to process this response, but we’ll use model entities that we can use to deserialize the JSON string into like the following:
public class Model {
    private long id;
    private long dt;
    private Clouds clouds;
    private Coord coord;
    private Wind wind;
    private String cod;
    private String visibility;
    private long timezone;
    private Sys sys;
    private String name;
    private String base;
    private List<Weather> weather = new ArrayList<>();
    private Main main;
    // Getters & setters
}
public class Clouds {
    private String all;
    // Getters & setters
}
public class Coord {
    private float lon;
    private float lat;
    // Getters & setters
}
public class Main {
    private float humidity;
    private float pressure;
    private float temp_max;
    private float temp_min;
    private float temp;
    // Getters & setters
}
public class Sys {
    private String message;
    private String id;
    private long sunset;
    private long sunrise;
    private String type;
    private String country;
    // Getters & setters
}
public class Weather {
    private int id;
    private String icon;
    private String description;
    private String main;
    // Getters & setters
}
public class Wind {
    private float speed;
    private float deg;
    // Getters & setters
}

Case 1: Jackson

A very popular framework for deserializing JSON into a Java object is the Jackson project, that provides a JSON Java parser (https://github.com/FasterXML/jackson).

We can simply add the dependency to our project:
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
 (for Maven)
dependencies {
    implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"
}
(for Gradle)
Now we can complete our retrieveWeather call:
private void retrieveWeather() {
    try {
            String restUrl =
                "https://api.openweathermap.org/data/2.5/weather?appid="
                + API_KEY + "&q=" + CITY;
            ObjectMapper objectMapper = new ObjectMapper();
            Model model = objectMapper.readValue(
                 new URL(restUrl), Model.class);
            updateModel(model);
        } catch (Throwable e) {
            System.out.println("Error: " + e);
            e.printStackTrace();
        }
    }
    private void updateModel(Model model) throws MalformedURLException, URISyntaxException {
        if (model != null) {
            if (!model.getWeather().isEmpty()) {
                Weather w = model.getWeather().get(0);
                imageView.setImage(new Image(new URL("http://openweathermap.org/img/wn/" + w.getIcon() + "@2x.png").toURI().toString()));
                weatherLabel.setText(w.getMain());
                descriptionLabel.setText(w.getDescription());
            }
            tempLabel.setText(String.format("%.2f °C - %.1f%%", model.getMain().getTemp() - 273.15, model.getMain().getHumidity()));
        }
    }
Now we run the application as shown in Figure 9-12.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig12_HTML.jpg
Figure 9-12

The application running

Notice that there is a synchronous call to do the whole process of querying the given URL, waiting for the result, and retrieving and parsing the response. If anything goes wrong, like a timeout or a network error, there will be only an exception, and possibly the UI will freeze, so we should wrap that call in a JavaFX service.

Case 2: Connect

Gluon’s Connect library (https://github.com/gluonhq/connect) not only deserializes the JSON response like Jackson but also does it in an asynchronous way; and, what’s more, it returns a JavaFX observable list or object that can be used directly by the JavaFX UI controls.

We can add the dependency to our project:
<dependency>
    <groupId>com.gluonhq</groupId>
    <artifactId>connect</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.json</artifactId>
    <version>1.1.5</version>
    <scope>runtime</scope>
</dependency>
 (for Maven)
dependencies {
    implementation "com.gluonhq:connect:2.0.1"
    runtimeOnly 'org.glassfish:jakarta.json:1.1.5
}
(for Gradle)
And now our updateWeather call can be done as follows:
    private void retrieveWeather() {
        GluonObservableObject<Model> weather = getWeather();
        weather.setOnFailed(e -> System.out.println("Error: " + e));
        weather.setOnSucceeded(e -> updateModel(weather.get()));
    }
    private GluonObservableObject<Model> getWeather() {
        RestClient client = RestClient.create()
                .method("GET")
                .host("http://api.openweathermap.org/")
                .connectTimeout(10000)
                .readTimeout(1000)
                .path("data/2.5/weather")
                .header("accept", "application/json")
                .queryParam("appid", API_KEY)
                .queryParam("q", CITY);
        return DataProvider.retrieveObject(
             client.createObjectDataReader(Model.class));
    }
The application we have created is very basic, but you get the point that it can be much more flexible and dynamic than simply rendering a web site in a WebView control. By accessing external APIs, the code in the JavaFX application looks similar to code in a backend that accesses those external APIs, as shown in Figure 9-13.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig13_HTML.jpg
Figure 9-13

The JavaFX application looks similar to code in a backend that accesses external APIs

There is a very important difference between the case where the client code resides in a cloud environment and on a desktop with an end user. In the first case, the cloud environment can in most situations be considered as a trusted environment. However, in the case where the code resides on the end user desktop, this is no longer true. The end user himself, or other malicious applications on the device, might obtain the API key.

In the case of OpenWeather queries, this is probably not the most fatal issue, but in general, API keys that provide access to critical or sensitive functionality should not be stored on end user systems.

In order to fix this issue, it is beneficial to use a middleware component that acts as a bridge between the trusted cloud infrastructure and the client device (desktop/mobile/embedded) of the user.

This middleware component can be extended and export general cloud functionality (e.g., access to a serverless container) as well, as shown in Figure 9-14.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig14_HTML.jpg
Figure 9-14

The middleware component can be extended to export general cloud functionality

In the next paragraph, we will explain how to write the OpenWeather application using Gluon CloudLink (https://docs.gluonhq.com/cloudlink/), which is a middleware solution from Gluon that provides specific support for JavaFX applications.

Case 3: CloudLink

Before we add the code, we need to access the Gluon Dashboard (https://gluon.io), access with our credentials, and go to API Management to add a remote function:

Then we add two query parameters for the request: appid and q. We set the value for the former with our API key, so the client doesn’t need to do it, while the value for the latter will be set in the client. It is important to realize that the API key is stored in the middleware (hosted in the cloud) and only sent between the middleware and the remote service. The client application is not accessing this API key; hence, the risk for the key being obtained by a hacker is minimized.

We can set a pair of values in the test fields, so we can directly test the remote function from the dashboard shown in Figure 9-15.
../images/468104_1_En_9_Chapter/468104_1_En_9_Fig15_HTML.jpg
Figure 9-15

Testing the function from the Gluon Dashboard

Once the test is successful (we should see a 200 response and a JSON string with the result), we can go back to our project and add the CloudLink dependencies:
repositories {
    mavenCentral()
    maven {
       url 'https://nexus.gluonhq.com/nexus/content/repositories/releases/'
    }
}
dependencies {
    implementation "com.gluonhq:charm-cloudlink-client:6.0.1"
    runtimeOnly 'org.glassfish:jakarta.json:1.1.5:module'
    implementation "com.gluonhq.attach:storage:4.0.2:desktop"
    runtimeOnly "com.gluonhq.attach:util:4.0.2"
}
And modify the code to:
    private void retrieveWeather() {
        GluonObservableObject<Model> weather = getWeather();
        weather.setOnFailed(e -> System.out.println("Error: " + e));
        weather.setOnSucceeded(e -> updateModel(weather.get()));
    }
    private GluonObservableObject<Model> getWeather() {
        RemoteFunctionObject functionObject = RemoteFunctionBuilder
                .create("weather")
                .param("q", CITY)
                .object();
        return functionObject.call(Model.class);
    }

As you can see, there is only one query parameter in the remote function call from the client.

The only credentials required within the client are those to access the CloudLink middleware. A file named gluoncloudlink_config.json is required at src/main/resources:
{
  "gluonCredentials": {
    "applicationKey" : "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "applicationSecret": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  }
}

The content of this file can be retrieved from the Dashboard ➤ Credentials ➤ Client.

Conclusion

JavaFX applications are regular Java applications and can use all existing Java functionality. Using the JavaFX WebView control, it is possible to render web pages in a JavaFX application and create interactions between the web page functionality (in JavaScript) and the Java engine running on the client.

For fine-grained, secure, and flexible integrations of client applications with enterprise functionality in the cloud, existing Java frameworks can be used. Developers should be very aware of the specific characteristics of client systems, especially related to security. Therefore, middleware (e.g., Gluon CloudLink) allows developers to shield the security-sensitive information from the client application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.197