Forms in TornadoFX

Any kind of user oriented application whether it’s a web application, a mobile application or a desktop application, there will be interaction with the user. That interaction is usually some kind of “fill in these fields please, so something can happen”.

KeepassFX has several such interactions. And of course they are implemented with tornadoFX builders. The nice thing about developing keepassFX while constantly talking in the Slack tornadoFX channel means that usecases I encounter will very often result in updates in the builders.

The form builder

A Form in TornadoFX basically looks like this:

class MainView : View("Basic form") {
    val model = ViewModel()
    val username = model.bind { SimpleStringProperty() }
    val password = model.bind { SimpleStringProperty() }

    override val root = form {
        fieldset {
            field("username") {
                textfield(username).required()
            }
            field("password") {
                passwordfield(password).required()
            }
            buttonbar {
                button("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE).setOnAction {
                    println("do nothing")
                }
                button("OK", ButtonBar.ButtonData.OK_DONE).setOnAction {
                    model.commit {
                        println("do something")
                    }
                }
            }
        }
    }
}

The result looks like this:

Several things to note about this code

the model

Normally a ViewModel() whould be used to create a class that contains properties and is linked to a class like a data class. We found out that for several usecases you don’t actually need them all the time. The code

val model = ViewModel()
    val username = model.bind { SimpleStringProperty() }
    val password = model.bind { SimpleStringProperty() }

creates an unlinked model that binds 2 SimpleStringProperties together.

validators

textfield(username).required() has the validator required(). Validators can only be used on properties that are part of a ViewModel(). Because of the validators, the code for the OK button can contain the model.commit function. This ensures that the code inside the model.commit block will only be executed when all the validators are happy. If you press OK button the validators will decorate the fields that do not pass validation:

Since this afternoon there is another way of doing this. button has the function enableWhen and disableWhen. Now you can link this to the validation state.

With this the OK button will only be enabled if all the validators are happy. Now the code looks like this:

class EnabledView : View("Basic form") {
    val model = ViewModel()
    val username = model.bind { SimpleStringProperty() }
    val password = model.bind { SimpleStringProperty() }

    override val root = form {
        fieldset {
            field("username") {
                textfield(username).required()
            }
            field("password") {
                passwordfield(password).required()
            }
            buttonbar {
                button("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE).setOnAction {
                    println("do nothing")
                }
                button("OK", ButtonBar.ButtonData.OK_DONE) {
                    enableWhen { model.valid }
                    setOnAction {
                        println("do something")
                    }
                }
            }
        }
        model.validate(decorateErrors = false)
    }
}

For more information about tornadoFX read the excellent guide at TornadoFX guide. To read more about the validators see Editing models and validation

comments powered by Disqus
comments powered by Disqus