Best Practices for JavaFX 2.0 Enterprise Applications (Part Two)

By James L. Weaver
Published May 2012

This article, which is part two of a two-part series, focuses on using best practices for developing enterprise applications in JavaFX 2.0. 

Downloads:

Introduction

JavaFX 2.0 is an API and runtime for creating Rich Internet Applications (RIAs). JavaFX was introduced in 2007, and version 2.0 was released in October 2011. One of the advantages of JavaFX 2.0 is that the code can be written in the Java language using mature and familiar tools.

This article, which is part two of a two-part series, focuses on using best practices for developing enterprise applications in JavaFX 2.0.

Overview of the TweetBrowser Application

To illustrate more techniques and best practices for enterprise application development in Java 2.0, we’ll continue examining the TweetBrowser application. A screen shot of this application shown in Figure 1.

The TweetBrowser project, which you'll download in the next section, contains the code for this application, portions of which we’ll highlight during the course of this article.

Figure 1: Screen Capture of the TweetBrowser Application at Startup

Figure 1: Screen Capture of the TweetBrowser Application at Startup

As a review from Part 1, when you click a #hashtag or @screenname, a spinning progress indicator appears in the upper right corner of the UI indicating that a search is in progress. You can also type a #hashtag, @screenname, or word in the text field and press the Enter key or click the Search button. Either way, the Search button’s appearance changes to an X to indicate that you can cancel the search by clicking the button, and most of the UI is disabled.

Clicking a Web link in a tweet results in a pop-up window that contains a WebView with the chosen page.

Obtaining and Running the TweetBrowser Project

  • Download the NetBeans project file [Zip], which includes the TweetBrowser project
  • Expand the project into a directory of your choice.
  • Start NetBeans, and select File -> Open Project.
  • From the Open Project dialog box, navigate to your chosen directory and open the TweetBrowser project, as shown in Figure 2. If you receive a message stating that the jfxrt.jar file can't be found, click the Resolve button and navigate to the rt/lib folder subordinate where you installed the JavaFX 2.0 SDK.

Note: You can obtain the NetBeans IDE from the NetBeans site.

Figure 2: Opening the TweetBrowser Project in NetBeans

Figure 2: Opening the TweetBrowser Project in NetBeans

  • To run the application, click the Run Project icon on the toolbar, or press the F6 key. The Run Project icon looks like the Play button on a media (for example, DVD) player, as shown in Figure 3.

Figure 3: Running the TweetBrowser Program in NetBeans

Figure 3: Running the TweetBrowser Application in NetBeans

The TweetBrowser application should appear in a window, as shown previously in Figure 1. Go ahead and play with the application, navigating screen names, hashtags, and Web links. We’ll analyze the application and walk through some code next.

Reviewing the TweetBrowser Application

Before diving into the code, let’s review the pieces shown in Figure 4, which comprise the TweetBrowser application.

Figure 4: Diagram of the TweetBrowser Application

Figure 4: Diagram of the TweetBrowser Application

Starting from the top right of Figure 4, the TweetBrowser home page is where the user launches the application by clicking the bug-eyed bird icon. This causes the application to be invoked via Java Web Start if an instance of TweetBrowser isn’t already running. Please refer to the “See Also” section for the URL of the TweetBrowser home page.

At the bottom left of Figure 4 is a representation of the javafxpert.tweetbrowser.ui package (shortened to tweetbrowser.ui in the diagram to conserve space), which contains two Java classes and a JavaFX cascading style sheet (CSS):

  • TweetBrowserMain is the main class of our JavaFX application, extending Application and containing main() and start() methods. Its role in the TweetBrowser application is to create a scene and populate it with the ToolBar and ListView shown in Figure 1. In addition, TweetBrowserMain creates and conditionally shows a pop-up window that contains a WebView when a Web link in a tweet is clicked.
  • TweetCell is a class that extends ListCell and renders the representation of a tweet shown in Figure 1. There is one TweetCell for each tweet, and the TweetCell instances are contained by the ListView.
  • tweetbrowser.css is a JavaFX CSS that styles various nodes in the application, including the Back and Search buttons and the screen name of the user that authored a tweet.

At the bottom center of Figure 4 is a representation of the javafxpert.tweetbrowser.model package, which contains three Java classes:

  • TweetBrowserModel is the primary model class of our application, and it contains properties that represent the state of the application. It also contains methods that utilize the REST/FX library, shown in the lower-right corner of Figure 4, to query the Twitter API. REST/FX is a library external to JavaFX 2.0, leveraged here for communicating with the Twitter REST endpoints and parsing its JSON responses. The “See Also” section at the end of this article has a link to the REST/FX project.
  • Tweet is a class that represents a tweet and follows the conventions prescribed for JavaFX properties. For example, Tweet contains a StringProperty named id that is accessible via the setId(), getId(), and idProperty() methods.
  • HistoryStack implements a FIFO stack, managing the navigation history of hashtags, words, and screen names. The Back button uses this history to determine which hashtag, word, or screen name to display when it is clicked.

As indicated in Figure 4, classes in the javafxpert.tweetbrowser.ui package invoke methods of classes in the javafxpert.tweetbrowser.model package. In addition, classes in the javafxpert.tweetbrowser.ui package render the state of the model to the UI, largely by utilizing the binding capabilities of JavaFX.

More Techniques and Best Practices Used in the TweetBrowser Application

In Part One of this series, some techniques and best practices used in the TweetBrowser application were discussed, namely the following:

  • Invoking an application via Java Web Start from the application’s home page
  • Ensuring only one instance of the application is started
  • Binding the UI to the model

In this article, the following additional techniques and best practices used in the TweetBrowser application are discussed:

  • Leveraging a JavaFX cascading style sheet
  • Implementing springs and struts in the UI
  • Using a ternary operation in binding expressions
  • Defining JavaFX properties
  • Leveraging a Popup to implement a dialog box
  • Using WebView to display a Web page

We’ll start with the first item in the list: leveraging a JavaFX cascading style sheet.

Leveraging a JavaFX Cascading Style Sheet

One of the very powerful features of JavaFX is the ability to modify the appearance of an application with the use of JavaFX CSS. To associate a style sheet with the TweetBrowser application’s Scene, the stylesheets() method of the SceneBuilder class is employed. This is shown in the code excerpt from TweetBrowserMain.java in Listing 1.




    Scene scene = SceneBuilder.create()
      .width(1000)
      .height(660)
      .stylesheets("javafxpert/tweetbrowser/ui/tweetbrowser.css")
      .root(
        BorderPaneBuilder.create()
          .top(createToolBar())
          .center(createListView())
          .build()
      )
      .build();


Listing 1: Associating a Style Sheet to the Scene

As an example of using a style sheet, notice the appearance and padding around the bolded label in the upper right side of Figure 1. This is partially due to the #tokenLabel selector in the tweetbrowser.css style sheet, shown in Listing 2.



#backButton {
    -fx-padding: 3 3 3 3;
}

#searchButton {
    -fx-padding: 4 4 4 4;
}

#tokenLabel {
  -fx-font-size: 14pt;
  -fx-font-weight: bold;
  -fx-label-padding: 0 5 0 15
}

#screenNameHyperlink {
  -fx-font-weight: bold;
}

#createdAtLabel {
  -fx-font-size: 10pt;
}

#popupButtonBar {
  -fx-padding: 0 0 8 0
}

Listing 2: The tweetbrowser.css Style Sheet

The #tokenLabel is associated with the rule in the style sheet through the use of the id property, as shown toward the end of Listing 3.



  private ToolBar createToolBar() {
    Region strut = new Region();
    Region spring = new Region();
    ImageView searchImageView = ImageViewBuilder.create()
      .image(new Image(getClass()
                       .getResourceAsStream("images/search-16.png")))
      .build();
    ImageView cancelImageView = ImageViewBuilder.create()
      .image(new Image(getClass()
                       .getResourceAsStream("images/bullet_cross.png")))
      .build();
    ToolBar toolBar = ToolBarBuilder.create()
      .items(
        backButton = ButtonBuilder.create()
          .id("backButton")
          .graphic((new ImageView(
            new Image(getClass()
                      .getResourceAsStream("images/nav-back-16.png"))))
          )
          .onAction(new EventHandler<ActionEvent>() {
              @Override public void handle(ActionEvent e) {
                if (!TweetBrowserModel.instance.historyStack
                    .oneRemainingProperty.get()) {
                  TweetBrowserModel.instance.historyStack.pop();
                }
                String peekedStr = TweetBrowserModel.instance.historyStack
                                                             .peek();
                invokeSearch(peekedStr, false);
              }
            })
          .build(),
        HBoxBuilder.create()
          .spacing(5)
          .children(
            searchTextField = TextFieldBuilder.create()
              .prefColumnCount(15)
              .onAction(new EventHandler<ActionEvent>() {
                @Override public void handle(ActionEvent e) {
                  invokeSearch(searchTextField.getText(), true);
                  searchTextField.setText("");
                }
              })
              .build(),
            searchButton = ButtonBuilder.create()
              .id("searchButton")
              .onAction(new EventHandler<ActionEvent>() {
                @Override public void handle(ActionEvent e) {
                  invokeSearch(searchTextField.getText(), true);
                  searchTextField.setText("");
                }
              })
              .build()
          )
          .build(),
        strut,
        currentTokenLabel = LabelBuilder.create()
          .id("tokenLabel")
          .build(),
        spring,
        GroupBuilder.create()
          .children(
            progressIndicator = ProgressIndicatorBuilder.create()
              .scaleX(0.7)
              .scaleY(0.7)
              .progress(-1.0)
              .build()
          )
          .build()
      )
      .build();

    backButton.disableProperty().bind(TweetBrowserModel.instance.historyStack
              .oneRemainingProperty.or(TweetBrowserModel.instance.queryActive
              .or(TweetBrowserModel.instance.webViewPopupVisible)));
    currentTokenLabel.textProperty().bind(TweetBrowserModel.instance
                     .historyStack.topProperty);
    searchButton.graphicProperty().bind(
            new When(TweetBrowserModel.instance.queryActive)
                     .then(cancelImageView)
                     .otherwise(searchImageView));
    searchButton.disableProperty()
        .bind(searchTextField.textProperty()
        .isEqualTo("").and(TweetBrowserModel.instance.queryActive.not()));

    strut.setPrefWidth(200);
    strut.setMinWidth(Region.USE_PREF_SIZE);
    strut.setMaxWidth(Region.USE_PREF_SIZE);
    HBox.setHgrow(spring, Priority.ALWAYS);
    return toolBar;
  }  

Listing 3: The TweetBrowserMain.java createToolBar() Method

For more information on using JavaFX CSS, please refer to the “See Also” section at the end of this article.

Implementing Springs and Struts in the UI

JavaFX has very powerful features for laying out the user interface. These features enable an application to appear the way you want it to appear, regardless of the size of the scene or the type of platform.

One of these features incorporates a concept that dates back to the early days of cross-platform development, known as springs and struts. The TweetBrowser application implements this concept in its toolbar to control the horizontal spacing between three of its nodes as the toolbar is horizontally resized. Figure 5 illustrates this, showing that there is a fixed horizontal strut between the rightmost button and the label, and there is a variable horizontal spring between the label and the progress indicator.

Figure 5: Implementing Springs and Struts

Figure 5: Implementing Springs and Struts

Listing 4 shows some code snippets from Listing 3 which reveal that the strut and the spring are each Region instances.



    Region strut = new Region();
    Region spring = new Region();

    ...

    ToolBar toolBar = ToolBarBuilder.create()
      .items(
        ...
        strut,
        currentTokenLabel = LabelBuilder.create()
          .id("tokenLabel")
          .build(),
        spring,
        ...
      )
      .build();

    ...

    strut.setPrefWidth(200);
    strut.setMinWidth(Region.USE_PREF_SIZE);
    strut.setMaxWidth(Region.USE_PREF_SIZE);

    HBox.setHgrow(spring, Priority.ALWAYS);
  

Listing 4: The strut and spring Are Region Instances

In Listing 4, the preferred width for the strut is set to 200, and the minimum and maximum widths are set to use the preferred width. The spring is given the priority to have additional horizontal space allocated to it when its container (in this case the toolbar) is resized larger than its preferred width.

Using a Ternary Operation in Binding Expressions

In Part One of this series we discussed binding the UI to the model and pointed out a couple of relevant examples. In this article, we highlight a more complex binding expression that behaves like a ternary operator. 

The following excerpt from Listing 3 contains an example of this binding technique, characterized by the use of the When class:




        searchButton.graphicProperty().bind(

            new When(TweetBrowserModel.instance.queryActive)

                     .then(cancelImageView)

                     .otherwise(searchImageView));

As a result of this bind expression, when the queryActive property of the model is true, the graphicProperty of the searchButton will be cancelImageView; otherwise, it will be searchImageView.

Defining JavaFX Properties

The previous code excerpt demonstrated the use of properties that exist in the JavaFX API, such as the graphic property of Button (that it inherits from Labeled class). It is often useful to define your own properties in an application, as is the case with the Tweet class in the TweetBrowser application. Listing 5 shows the definition of the Tweet class, which conforms to the JavaFX property method naming conventions.



package javafxpert.tweetbrowser.model;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * A JavaFX property that represents a tweet in the TweetBrowser program
 */
public final class Tweet {
  private StringProperty id;
  final public void setId(String value) { 
    idProperty().set(value); 
  }
  final public String getId() { 
    return idProperty().get(); 
  }
  final public StringProperty idProperty() { 
    if (id == null) {
      id = new SimpleStringProperty(this, "id");
    }
    return id; 
  } 

  private StringProperty text;
  final public void setText(String value) { 
    textProperty().set(value); 
  }
  final public String getText() { 
    return textProperty().get(); 
  }
  final public StringProperty textProperty() { 
    if (text == null) {
      text = new SimpleStringProperty(this, "text");
    }
    return text; 
  }

  private StringProperty profileImageUrl;
  final public void setProfileImageUrl(String value) { 
    profileImageUrlProperty().set(value); 
  }
  final public String getProfileImageUrl() { 
    return profileImageUrlProperty().get(); 
  }
  final public StringProperty profileImageUrlProperty() { 
    if (profileImageUrl == null) {
      profileImageUrl = new SimpleStringProperty(this, "profileImageUrl");
    }
    return profileImageUrl; 
  }

  private StringProperty userName;
  final public void setUserName(String value) { 
    userNameProperty().set(value); 
  }
  final public String getUserName() { 
    return userNameProperty().get(); 
  }
  final public StringProperty userNameProperty() { 
    if (userName == null) {
      userName = new SimpleStringProperty(this, "userName");
    }
    return userName; 
  }

  private StringProperty screenName;
  final public void setScreenName(String value) { 
    screenNameProperty().set(value); 
  }
  final public String getScreenName() { 
    return screenNameProperty().get(); 
  }
  final public StringProperty screenNameProperty() { 
    if (screenName == null) {
      screenName = new SimpleStringProperty(this, "screenName");
    }
    return screenName; 
  }

  private StringProperty createdAt;
  final public void setCreatedAt(String value) { 
    createdAtProperty().set(value); 
  }
  final public String getCreatedAt() { 
    return createdAtProperty().get(); 
  }
  final public StringProperty createdAtProperty() { 
    if (createdAt == null) {
      createdAt = new SimpleStringProperty(this, "createdAt");
    }
    return createdAt; 
  }

  public Tweet(String id, String text, String profileImageUrl, 
               String screenName, String userName, String createdAt) {
    setId(id);
    setText(text);
    setProfileImageUrl(profileImageUrl);
    setScreenName(screenName);
    setUserName(userName);
    setCreatedAt(createdAt);
  }

  public String toString() {
    return text.getValue();
  }
}

Listing 5: Defining a Property in Tweet.java

The Tweet class encapsulates six string properties that are relevant to tweets retrieved from the Twitter API. Defining this class using the JavaFX property method naming conventions allows it to play well with classes that expect JavaFX properties and to participate in binding expressions. For more information on JavaFX properties and binding, please refer to the “See Also” section.

Leveraging a Popup to Implement a Dialog Box

As previously mentioned, when the user clicks a Web link in the text of a tweet, a popup appears that contains a WebView with the chosen page. Listing 6 contains the code that populates and instantiates the Popup.



  private void createWebViewPopup() {
    WebView webView;
    webView = WebViewBuilder.create()
      .prefWidth(920)
      .prefHeight(560)
      .build();    
    TweetBrowserModel.instance.webViewPopupWebEngine = webView.getEngine();

    Button okButton;
    webViewPopup = PopupBuilder.create()
      .content(
        StackPaneBuilder.create()
          .children(
            RectangleBuilder.create()
              .width(930)
              .height(620)
              .arcWidth(20)
              .arcHeight(20)
              .fill(Color.WHITE)
              .stroke(Color.GREY)
              .strokeWidth(2)
              .build(),
            BorderPaneBuilder.create()
              .center(
                GroupBuilder.create()
                  .children(webView)
                  .build()
              )
              .bottom(
                HBoxBuilder.create()
                  .id("popupButtonBar")
                  .alignment(Pos.CENTER)
                  .spacing(10)
                  .children(
                    ButtonBuilder.create()
                      .id("backButton")
                      .graphic((new ImageView(
                        new Image(getClass()
                            .getResourceAsStream("images/nav-back-16.png"))))
                      )
                      .onAction(new EventHandler<ActionEvent>() {
                          @Override public void handle(ActionEvent e) {
                            TweetBrowserModel.instance.webViewPopupWebEngine
                                           .executeScript("history.go(-1)");
                          }
                        })
                      .build(),
                    okButton = ButtonBuilder.create()
                      .text("OK")
                      .onAction(new EventHandler<ActionEvent>() {
                        @Override public void handle(ActionEvent e) {
                          TweetBrowserModel.instance.webViewPopupVisible
                                                    .setValue(false);
                        }
                      })
                      .build() 
                  )
                  .build()
              )
              .build()
          )
          .build()
      )
      .build();

    BorderPane.setAlignment(okButton, Pos.CENTER);
    BorderPane.setMargin(okButton, new Insets(10, 0, 10, 0));

    TweetBrowserModel.instance.webViewPopupVisible
                              .addListener(new ChangeListener() {
      public void changed(ObservableValue ov, Object oldVal, Object newVal) {
        if ((Boolean)newVal) {
          webViewPopup.show(stage, 
            (stage.getWidth() - webViewPopup.getWidth()) / 2 + stage.getX(), 
            (stage.getHeight() - webViewPopup.getHeight()) / 2 + stage.getY());
        }
        else {
          webViewPopup.hide();
        }
      }
    });
  }      
}

Listing 6: The TweetBrowserMain.java createWebViewPopup() Method

The createWebViewPopup() is invoked only once in the application: at startup in the start() method. Therefore, only one instance exists, and its visibility is controlled by adding the ChangeListener shown in Listing 6 to the webViewPopupVisible property of the model. When webViewPopupVisible becomes true, the show() method of the Popup instance is invoked, passing in a location relative to the application window. When webViewPopupVisible becomes false, the hide() method of the Popup instance is invoked. 

Looking at Listing 6 again, you’ll notice that webViewPopupVisible is set to false when the OK button is clicked. Listing 7 contains an excerpt from TweetCell.java showing that webViewPopupVisible is set to true when a Hyperlink containing a Web link is clicked without the Shift key being held down.



      else if (word.startsWith("http") && word.length() > 12) {
        wordNode =  HyperlinkBuilder.create()
          .text(word)
          .onMouseClicked(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent me) {
              if (me.isShiftDown()) {
                URL url;
                try {
                  url = new URL(word);
                } 
                catch (MalformedURLException exception) {
                  throw new RuntimeException(exception);
                }

                try {
                  java.awt.Desktop.getDesktop().browse(url.toURI());
                } 
                catch (URISyntaxException exception) {
                  throw new RuntimeException(exception);
                } 
                catch (IOException exception) {
                  throw new RuntimeException(exception);
                }
              }
              else {
                TweetBrowserModel.instance.webViewPopupWebEngine.load(null);
                TweetBrowserModel.instance.webViewPopupWebEngine.load(word);
                TweetBrowserModel.instance.webViewPopupVisible
                                          .setValue(true);
              }
            }
          })
          .build();
      }

Listing 7: Creating and Handling a Hyperlink That Contains a Web Link

Note that Listing 7 also demonstrates the use of the java.awt.Desktop class for pointing the default browser to a desired URL.

Using WebView to Display a Web Page

As shown in Listing 7, the load() method of the WebEngine referenced by the webViewPopupWebEngine variable in the model is invoked. It is invoked the first time with a null argument to blank out the WebView, and it is invoked with the desired URL the second time.

The purpose for invoking it the first time is so the WebView doesn’t retain its contents from the last time it was visible while it is loading the desired page. Note that the webViewPopupWebEngine variable refers to a WebEngine instance as a result of the following excerpt from Listing 6:

TweetBrowserModel.instance.webViewPopupWebEngine = webView.getEngine();

Conclusion

There are many techniques and best practices that can be used in JavaFX applications, including leveraging a JavaFX cascading style sheet, implementing springs and struts in the UI, using a ternary operation in binding expressions, defining JavaFX properties, leveraging a Popup to implement a dialog box, and using WebView to display a Web page. 

In addition, the techniques highlighted in Part One of this series bear repeating here: invoking an application via Java Web Start from the application’s home page, ensuring only one instance of the application is started, and binding the UI to the model. 

These certainly aren’t exhaustive lists, and by studying TweetBrowser as well as other JavaFX applications independently, you’ll pickup other techniques and best practices. For example, studying the TweetBrowserModel class will help you make an informed decision about whether to use the REST/FX libraries to invoke REST services in your applications. Also, studying the HistoryStack class and its usage in TweetBrowser might give you some ideas on implementing Back button navigation in future applications.

See Also