What You’ll Learn

  1. How to define a business rule
  2. How to link a business rule to the Model with a Binding
  3. How to control a Button with Binding
  4. How to keep the business rules out of the View

Next Problem - Empty Fields

We still have a functional problem with our application. It’s possible to save a customer without specifying an account number or a name. We’ll need to prevent this.

Empty Fields is a Business Rule

Here’s a very important concept: Validation rules about data and when things can be saved are business rules. They cannot be defined in the View, they need to be defined in the Interactor.

How do these rules get to the View, then?

Through the Model!

This means that we’re going to need to create something in the model that says, “It’s OK to save”:

public class CustomerModel {

    private final StringProperty accountNumber = new SimpleStringProperty("");
    private final StringProperty customerName = new SimpleStringProperty("");
    private final BooleanProperty okToSave = new SimpleBooleanProperty(false);
}

I’ve left out all of the getter and setter stuff, because I’m sure you understand how that all works. We have a new BooleanProperty called okToSave.

How does CustomerModel.okToSave get set?

Through the Interactor. We’re going to bind okToSave to the other fields through custom Binding that makes sure that all of the fields have valid values:

public CustomerInteractor(CustomerModel model) {
    this.model = model;
    model.okToSaveProperty().bind(Bindings.createBooleanBinding(this::isDataValid, model.accountNumberProperty(), model.customerNameProperty()));
}

private boolean isDataValid() {
    return !model.getAccountNumber().isEmpty() && !model.getCustomerName().isEmpty();
}

Let’s take a look at isDataValid() first. It’s straight-forward method that just looks at all of the fields in CustomerModel that must have a value in them before the data can be saved. If any one of them is empty, then it will return false.

In the constructor of the Interactor we’re going to bind CustomerModel.okToSave to the other fields through a BooleanBinding. We’re using the Bindings library, which has huge number of static methods to create different kinds of Bindings for us. Here we’re using Bindings.createBooleanBinding().

Bindings.createBooleanBinding() takes at least two parameters, and can take many more. The first parameter is always a Supplier<Boolean>, and here we’re using a method reference to isDataValid(). The remaining parameters are the Properties that will trigger the Binding to recalculate whenever they change. In this case, it’s CustomerModel.accountNumber and CustomerModel.customerName.

This means that whenever either of those Properties change, the Binding will be recalculated and CustomerModel.okToSave will always be synchronized to them. In other words, okToSave will always have the correct value.

This is how we inject a business rule into the Model without putting the code in the Model. Now it’s available for the View to use.

Adding the Validation to the View

We are going to control the ability to save by disabling the “Save” Button when the data is not valid. We are going to do this with a Binding.

There’s a complication, though. We are already manually setting and unsetting Button.disable() as part of our Button action. So we’re going to need to deal with that:

private Node createButtons() {
    Button saveButton = new Button("Save");
    saveButton.disableProperty().bind(model.okToSaveProperty().not());
    saveButton.setOnAction(evt -> {
        saveButton.disableProperty().unbind();
        saveButton.setDisable(true);
        saveHandler.accept(() -> saveButton.disableProperty().bind(model.okToSaveProperty().not()));
    });
    HBox results = new HBox(10, saveButton);
    results.setAlignment(Pos.CENTER_RIGHT);
    return results;
}

Now we are creating the Button and then immediately binding it’s disable Property to CustomerModel.okToSave with the not() modifier. That’s pretty simple.

But we cannot directly set a Property that’s bound. We’ll get a runtime error if we do that. So in the Button onAction EventHandler we have to first unbind the Property, then set it to true. In the Runnable that we pass to saveHandler, we re-establish the binding instead of setting it to false.

Now it Works

That’s it, and it was pretty painless too.

You can see how we’ve established the business rule in the Interactor, connected it to the View via the Model, and the View uses it without having any knowledge about how it works.

Now, let’s look at it in action:

ScreenShot 1

The “Save” Button is disabled. Then with just account number filled out:

ScreenShot 2

It’s still disabled. And then with both fields completed:

ScreenShot 3

Now it’s enabled and save can happen. We’re done!