Skip to content

Utilities

Edvin Syse edited this page Mar 11, 2017 · 10 revisions

WikiDocumentationUtilities

Utilities

SortedFilteredList

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.

Filter predicate

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 }

Filter based on user input

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.

POJO Binding

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

Clone this wiki locally