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
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 thert/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
- 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 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
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, extendingApplication
and containingmain()
andstart()
methods. Its role in the TweetBrowser application is to create a scene and populate it with theToolBar
andListView
shown in Figure 1. In addition,TweetBrowserMain
creates and conditionally shows a pop-up window that contains aWebView
when a Web link in a tweet is clicked.TweetCell
is a class that extendsListCell
and renders the representation of a tweet shown in Figure 1. There is oneTweetCell
for each tweet, and theTweetCell
instances are contained by theListView
.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 aStringProperty
namedid
that is accessible via thesetId()
,getId()
, andidProperty()
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
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.