Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Thursday, September 10, 2015

JavaFX Binding 1 : A Conditional Address Form

If you're not using Binding in your JavaFX code, you're leaving a lot of functionality on the table.  This blog post presents a conditional address form where a selection of "Canada" presents a different set of fields than a selection of "US".  The conditional fields' visibleProperties are bound to one of two ToggleButtons.
While a ChangeListener with a some logic to issue setVisible() calls isn't tough to figure out, lots of anonymous classes or even lambdas starts to weigh down a class.  Difficulty reading becomes more significant as business logic is added to such listeners.  This post shows how to relate several fields to a user selection, producing a form that's tailored to a particular condition.

All code is available on GitHub here.

Binding

JavaFX components expose a lot of their configuration as properties.  Like a JavaBean property, a JavaFX property represents the internal state of a component.  However, JavaFX properties also include a change detection mechanism.  This extra mechanism enables the JavaFX component to be listened to or to have its state changed by another component.

Swing offers listeners on its components, but JavaFX has applied this broadly to the entire component rather than a single aspect like a value.

Conditional Address Form

This address form is driven by a pair of ToggleButtons "Canada" and "United States".  When Canada is pressed, the form supports fields for a Canadian address.  When "United States" is selected, the form supports fields for a US address.  Specifically, a drop down of provinces is shown for Canada and a drop of states plus a zip code extension is shown for the US.

The following video shows the form filled out for both address types and shows the action of the Toggle buttons.



The ToggleButton for Canada has a property "selected".  The Canadian province ChoiceBox has a property "visible".  I bind the visible property of the ChoiceBox to the selected state of the ToggleButton.

Similarly, there is a property for the US ToggleButton.  Both the US state ChoiceBox and the postal code extension have visible properties.  I've bound the visible property of this ChoiceBox and the postal code extension TextField to the selected state of the US ToggleButton.  There is also a Label "-" that is hidden when US is de-selected.

Component Relationships between ToggleButtons, ChoiceBoxes, and a TextField
I used SceneBuilder 2.0 to layout the user interface.  These are the fields of the class defined in the Controller.

@FXML
ToggleButton tbCanada;
@FXML
ToggleButton tbUS;
@FXML
TextField tfAddress1;
@FXML
TextField tfAddress2;

@FXML
TextField tfCity;

@FXML
Label lblCAProvince;
@FXML
ChoiceBox<Pair<String, String>> cbCAProvince;
@FXML
Label lblUSState;
@FXML
ChoiceBox<Pair<String, String>> cbUSState;
@FXML
TextField tfPostalCode;
@FXML
TextField tfPostalCodeExt;
@FXML

Label lblPostalCodeSep;

I define an @FXML initialize() method.  This is where I set up the relationship between the components.  The Canadian-specific visibleProperties are linked to the tbCanada ToggleButton's selected property.   The US-specific visibleProperties are linked to the bUS ToggleButton's selected property.

@FXML
public void initialize() {
//
// Controls specific to Canada
//
lblCAProvince.visibleProperty().bind( tbCanada.selectedProperty() );
cbCAProvince.visibleProperty().bind( tbCanada.selectedProperty() );
cbCAProvince.setItems( FXCollections.observableArrayList(provinces) );
cbCAProvince.setConverter(new PairStringConverter(provinces));
//
// Controls specific to US
//
lblUSState.visibleProperty().bind( tbUS.selectedProperty() );
cbUSState.visibleProperty().bind( tbUS.selectedProperty() );
cbUSState.setItems( FXCollections.observableArrayList(states) );
cbUSState.setConverter(new PairStringConverter(states));
tfPostalCodeExt.visibleProperty().bind(tbUS.selectedProperty());
lblPostalCodeSep.visibleProperty().bind(tbUS.selectedProperty());

}

There isn't a need to add any listeners, to query the state of controls (the ToggleButton), and to set the values of other controls (ChoiceBox, etc).  Although bind() might be unfamiliar initially, the whole thing reads nicely.  The code isn't cluttered with extra definitions of anonymous classes or lambdas.

Other Notes

All code is available on GitHub here.

For the contents of the ChoiceBoxes, I prepare ArrayLists of JavaFX Pairs.  The Pair is a key / value tuple.  I put province code / province name in for the Canadian list and state code / state name for the US list.  These lists are initialized in the constructor and attached via setItems() calls.

There is a special PairStringConverter that when registered will use the Pair's value (ex, "Virginia") instead of the toString() ("VA - Virginia") which is the default.

In Scene Builder, I attach a clearForm() event handler to each ToggleButton.  This clears the form to handle the case where the user partially fills out one country's form, changes to the other, then saves.  This might result in a Canadian province being submitted alongside a US state for a single address.

The save() operation is attached to the Save Button and simply outputs the form.


As both a Swing developer and a Web developer, my first instinct is to grab a listener and start calling setDisabled() or setVisible() when I want to dim parts of the UI.  Although it's easy to see what's going on in such a listener, even the best teams may have difficulty keeping the extra listener code consistent.  Someone may use an anonymous class, another developer may use a nested static class, another developer may use a public class, another may use a lambda, etc.  Binding makes the intent of the lines of code clear and the style is subject to less variation.

No comments:

Post a Comment