-
Notifications
You must be signed in to change notification settings - Fork 271
Utilities
Wiki ▸ Documentation ▸ Utilities
SortedFilteredList is a wrapper for an observable list of items that can be bound to a list control like TableView, ListView etc.
The wrapper makes the data sortable and filterable.
// Define a list of persons
val persons = personController.listAll()
// Create a table
val table = TableView<Person>()
// Wrap the list of persons and assign the sorted filtered list to the TableView
val data = SortedFilteredList(persons).bindTo(table)
The bindTo
function takes care of assigning the data to the items
property of the TableView, as well as connecting the comparatorProperty
of the TableView and the sorted data. You can optionally assign the tableView.items
manually instead.
Items can be updated by calling data.items.setAll
or data.items.addAll
at a later time if needed.
The filter predicate controls the visible items. It accepts an item and should returns a Boolean
to signal visibility. The list automatically refreshes when the predicate changes.
// Let the list show only adults
data.predicate = { it.age > 18 }
The filterWhen
function can be supplied with an observable value and a filter expression to make the predicate update whenever the observable value changes. This is very handy for filtering a list based on user input:
textfield {
promptText = "Filter search"
data.filterWhen(textProperty(), { query, item -> item.matches(query) } )
}
You can easily update the filter predicate based on any arbitrary mutable property by way of the filterWhen
function.
You can force a refilter by calling the refilter()
function.
For two way/bidirectional binding, JavaFX requires you to use JavaFX Properties. However, when working with legacy code you might already have domain objects that follows the POJO standard, or you simply might not care about bidirectionality.
This is often the case for scenarios where the underlying domain object never change via any other mechanism than user input during the course of a specific view of the data.
TornadoFX provides an easy way of binding inputs to POJOs. By supplying a domain object and a getter/setter pair, you will receive a correctly typed Observable
value that can be used for ui binding.
We will use a Java Class defined as a POJO in the following example:
public class JavaPerson {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The traditional approach would be something like this:
val nameField = TextField(person.name)
button("Save") {
setOnAction {
// Pull values from the UI and update the domain object
person.name = nameField.text
controller.save(person)
}
}
While this works, it's cumbersome and creates a lot of boiler plate for heavy forms. It's also bug prone. Changes to the data would not automatically be visible if the same data is displayed in other controls.
We can make sure the person object is always kept in sync with the UI by "converting" the name property to an observable. Let's create a complete person editor:
class PersonEditor(val person: Person) : Fragment() {
override val root = Form()
val controller : PersonController by inject()
init {
val nameProperty = observable(person, JavaPerson::getName, JavaPerson::setName)
val ageProperty = observable(person, JavaPerson::getAge, JavaPerson::setAge)
with (root.fieldset("Person Info")) {
fieldset("Name") {
textfield(nameProperty)
}
fieldset("Age") {
textfield(ageProperty, IntegerStringConverter())
}
button("Save") {
setOnAction {
// Person is already updated, just save :)
controller.save(person)
}
}
}
}
}
I know what you're thinking - that was even more code! And you might be correct. The binding and property creation could just as easily have been created inline:
textfield(observable(person, JavaPerson::getName, JavaPerson::setName))
If you're willing to trade type safety for more compact code, you can refer to the property by name:
textfield(observable(person, "name"))
If you use the same property in multiple places, or if you update the domain object from some other means than the UI, you might want to keep defining the properties up front like in the initial example. Then you can update the UI easily if some other mechanism changes your data:
button("Update from POJO") {
setOnAction {
// Set a new version behind the UI's back
person.id = 42
// Force an update to the UI
idProperty.refresh()
}
}
Next: TableView SmartResize