Search code examples
javajavafxproperty-binding

JavaFX how to check if all dynamically added textfields are empty


I have a TextField and two buttons in a row. One button (the 'add button') adds another row of a TextField and another pair of add and delete button, the other button deletes the row. The delete button is disabled while the current row is the only row, so there can't be no rows.

The add button is enabled only if the textfield of the current row is not empty and if it is the last textfield. So every row has a disabled 'add button' except for the last one.

My question now is how can I bind the 'add button' disableProperty to all textfields that exist and check if they are empty. As a matter of fact, I think I only have to check the last textfield and if it is empty I disable the last 'add button' if something is written the last 'add button' remains disabled but the current one of the row gets enabled.

I have found a workaround, in which I bind the button to the textfield, then if I add another row I unbind the button, disable it, and if I delete a row I only enable the last button and bind it againt to the textfield. This solution seems very clunky and I was wondering if there is a more elgant solution with property binding.

My code (with the workaround, so you can see what I want to do):

public class Controller {
@FXML
private VBox VBox;

public ObservableList<TextField> oList = FXCollections.observableArrayList();
public ObservableList<Button> bList = FXCollections.observableArrayList();

public void initialize(){
    createRow();
}

private void createRow(){
    HBox box = new HBox(10);
    TextField textField = new TextField();
    Button addButton = new Button("Add row");
    Button deleteButton = new Button("Delete");
    box.getChildren().addAll(textField, addButton, deleteButton);
    VBox.getChildren().add(box);

    oList.add(textField);
    bList.add(addButton);

    addButton.disableProperty().bind(Bindings.isEmpty(textField.textProperty()));

    addButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {
            addButton.disableProperty().unbind();
            createRow();
            textField.setDisable(true);
            addButton.setDisable(true);
        }
    });

    deleteButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {

            int idx = oList.indexOf(textField);
            oList.remove(idx);
            bList.remove(idx);
            VBox.getChildren().remove(idx);

            for(TextField tf : oList){
                int i = oList.indexOf(tf);
                if(oList.size()-1 == i){
                    tf.setDisable(false);
                    bList.get(i).disableProperty().bind(Bindings.isEmpty(tf.textProperty()));
                }
            }


        }
    });

}

}

And two Screenshots:

4 rows added, all 'add buttons' disabled, also the last one bc nothing is written in textfield

Here I deleted row test1 and the empty row and all 'add buttons' are still disabled except for the last one because there is text in the text field

Thanks for your help!

PS: I know in my Code ObservableLists aren't necessary, but I was trying things out and let them in because I forgot...


Solution

  • You can create a BooleanExpression that returns true if all TextFields in the list are empty. Note that you need to recreate that expression as the contents of the ObservableList<TextField>' change:

    oList.addListener((ListChangeListener<? super TextField>) c -> {
        addButton.disableProperty().unbind();
        BooleanExpression allEmpty = oList.stream()
                .map(tf -> BooleanExpression.booleanExpression(tf.textProperty().isEmpty()))
                .reduce(new SimpleBooleanProperty(true), BooleanExpression::and);
        addButton.disableProperty().bind(allEmpty);
    });
    

    Everytime a new TextField is added or removed, each TextField in the list will be mapped to a BooleanExpression of the empty property of that TextField. Then, all expressions will be anded together.

    Note: The same way can also be done using a loop instead of a stream.

    Note: You need to add this listener before adding any element to the list.