11. Editing Models and Validation Another big change in this version is that the data in the table does not update when we type into the text fields. This is because the model has exposed a copy of the properties from the person object and does not write back into the actual person object before we call model.commit() . This is exactly what we do in the save function. Once commit has been called, the data in the facade is flushed back into our person object and the table will now reflect our changes. Rollback Since the model holds a reference to the actual Person object, we can can reset the text fields to reflect the actual data in our Person object. We could add a reset button like this: button("Reset").action { } model.rollback() When the button is pressed, any changes are discarded and the text fields show the actual Person object values again. The PersonModel We never explained how the PersonModel works yet, and you probably have been wondering about how the PersonModel is implemented. Here it is: class PersonModel(var person: Person) : ViewModel() { val name = bind { person.nameProperty } val title = bind { person.titleProperty } } It can hold a Person object, and it has defined two strange-looking properties called name and title via the bind delegate. Yeah it looks weird, but there is a very good reason for it. The { person.nameProperty } parameter for the bind function is a lambda that returns a property. This returned property is examined by the ViewModel , and a new property of the same type is created. It is then put into the name property of the ViewModel . When we bind a text field to the name property of the model, only the copy is updated when you type into the text field. The ViewModel keeps track of which actual property belongs to which facade, and when you call commit the values from the facade are flushed into the actual backing property. On the flip side, when you call rollback the exact opposite happens: The actual property value is flushed into the facade. 158
11. Editing Models and Validation The reason the actual property is wrapped in a function is that this makes it possible to change the person variable and then extract the property from that new person. You can read more about this below (rebinding). Dirty Checking The model has a Property called dirty . This is a BooleanBinding which you can observe to enable or disable certain features. For example, we could easily disable the save button until there are actual changes. The updated save button would look like this: button("Save") { enableWhen(model.dirty) action { save() } } There is also a plain val called isDirty which returns a Boolean representing the dirty state for the entire model. One thing to note is that if the backing object is being modified while the ViewModel is also modified via the UI, all uncommitted changes in the ViewModel are being overridden by the changes in the backing object. That means the data in the ViewModel might get lost if external modification of the backing object takes place. val person = Person("John", "Manager") val model = PersonModel(person) model.name.value = "Johnny" person.name = "Johan" //modify the ViewModel //modify the underlying object println(" Person = ${person.name}, ${person.title}") //output: Person = Johan, Manager println("Is dirty = ${model.isDirty}") //output: Is dirty = false println(" Model = ${model.name.value}, ${model.title.value}") //output: Model = Johan, Manager As can be seen above the changes in the ViewModel got overridden when the underlying object was modified. And the ViewModel was not flagged as dirty . Dirty Properties 159
Table of Contents Introduction Part
Part 1: TornadoFX Fundamentals Part
1. Why TornadoFX? how Kotlin can si
1. Why TornadoFX? tableview { items
2. Setting Up 1.8 Then this goes
3. Components However, we might wan
3. Components Specify the name of y
3. Components class MyApp: App(MyVi
3. Components import javafx.scene.c
3. Components Here is a simple exam
3. Components The VBox contains a L
3. Components Argument Type Descrip
3. Components Argument Type Descrip
3. Components import tornadofx.* cl
3. Components Sometimes it is neces
4. Basic Controls Basic Controls On
4. Basic Controls 3. The Button is
4. Basic Controls If you need to sa
4. Basic Controls Figure 4.4 You ca
4. Basic Controls You do not need t
4. Basic Controls radiobutton("Powe
4. Basic Controls ProgressBar A Pro
4. Basic Controls Like most other c
4. Basic Controls Keep in mind that
4. Basic Controls button("Commit")
4. Basic Controls 51
5. Data Controls val greekLetters =
5. Data Controls class Person(id: I
5. Data Controls tableview(persons)
5. Data Controls This more closely
5. Data Controls The rowExpander()
5. Data Controls Let's break this d
5. Data Controls data class Departm
5. Data Controls val tableData = ma
5. Data Controls val numbers = (1..
6. Type Safe CSS Type-Safe CSS Whil
6. Type Safe CSS import javafx.scen
6. Type Safe CSS You can apply styl
6. Type Safe CSS Figure 6.3 Note al
6. Type Safe CSS label("Lore Ipsum"
6. Type Safe CSS If you want the bu
6. Type Safe CSS import javafx.scen
6. Type Safe CSS Modifier Selection
6. Type Safe CSS The DangerButtonSt
7. Layouts and Menus vbox { button(
7. Layouts and Menus Notice also wh
7. Layouts and Menus borderpane { l
7. Layouts and Menus 95
7. Layouts and Menus The example ab
7. Layouts and Menus One way to use
7. Layouts and Menus columnIndex: I
7. Layouts and Menus TabPane A TabP
7. Layouts and Menus Like many buil
OSGi class Dashboard : View() { ove
OSGi Requirements To run TornadoFX
Scopes Scopes Scope is a simple con
Scopes Now whenever you access the
Scopes Testing with Scopes Since Sc
EventBus A button in the UI can fir
EventBus When you create a subclass
EventBus Many feel that events migh
Workspaces Workspaces Java Business
Workspaces To keep things focused,
Workspaces fun TabPane.connectWorks
Workspaces Modifying the default wo
Workspaces Title and heading When a
Workspaces Navigating between docke
Workspaces We can see that the titl
Workspaces workspace.dockInNewScope
Workspaces A Workspace in Tabs mode
Workspaces // A Form based View we
Workspaces This could be a good ide
Layout Debugger Layout Debugger Whe
Layout Debugger yet, submit a pull
Internationalization You can add a
Config Settings and State Config se
Config Settings and State If the re
Config Settings and State var bool:
JSON and REST } } with(json) { add(
JSON and REST Basic operations Ther
JSON and REST To configure authenti
Dependency Injection Dependency Inj
Dependency Injection This initializ
Wizard class BasicData : View("Basi
Wizard When the Next or Finish butt
Wizard val wizard = find() wizard.s
Wizard class CustomerWizard : Wizar
Wizard Structural modifications The