Skip to content
This repository has been archived by the owner on Sep 28, 2024. It is now read-only.

Type Safe Builders

Thomas Nield edited this page Mar 7, 2016 · 31 revisions

WikiDocumentationType Safe Builders

Type Safe Builders

Builders are extension functions to the Java FX Pane class which enables you to create a new node, set some properties and add it to the children of the parent Pane with very little code. The hierarchical nature of the builders makes it easy to understand the ui composition with a simple glance.

There are builders for complex Node components as well, like TableView.

Builders also support automatic property binding for input type components like TextField, ComboBox etc.

The full list of available functions can be seen in the Builders.kt source file. If you miss a feature, send us a pull request, and we'll be happy to include it.

###Example 1 ####A VBox containing two HBox components

Each HBox contains a Label and a TextField, both which utilize a margin constraint within the HBox. The TextField components are also set to use the maximum allowable width.

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            hbox {
                label("First Name") {
                    hboxConstraints { margin = Insets(5.0) }
                }
                textfield {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
            hbox {
                label("Last Name") {
                    hboxConstraints { margin = Insets(5.0) }
                }
                textfield {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
        }
    }
}

Rendered UI

Note also you can add Node instances to a builder without using their builder equivalents This is helpful if you have Node components existing outside your builder or are working with Node items that have no builder support. You can then call the Kotlin stdlib function apply() to then maintain the "builder flow".

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            hbox {
                this += Label("First Name").apply {
                    hboxConstraints { margin = Insets(5.0) }
                }
                this += TextField().apply {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
            hbox {
                this += Label("Last Name").apply {
                    hboxConstraints { margin = Insets(5.0) }
                }
                this += TextField().apply {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
        }
    }
}

###Example 2

A TableView

You can build an entire TableView, complete with a backing list and column value mappings, with a succinct builder structure.

Say you have the given domain object Person.

class Person(id: Int, name: String, birthday: LocalDate) {
    var id by property<Int>()
    fun idProperty() = getProperty(Person::id)

    var name by property<String>()
    fun nameProperty() = getProperty(Person::name)

    var birthday by property<LocalDate>()
    fun birthdayProperty() = getProperty(Person::birthday)

    //assume today is 2016-02-28
    val age: Int get() = Period.between(birthday, LocalDate.now()).years

    init {
        this.id = id
        this.name = name
        this.birthday = birthday
    }
}

You can easily declare a TableView using a builder.

class MyView : View() {

    override val root = VBox()

    private val persons = FXCollections.observableArrayList<Person>(
            Person(1,"Samantha Stuart",LocalDate.of(1981,12,4)),
            Person(2,"Tom Marks",LocalDate.of(2001,1,23)),
            Person(3,"Stuart Gills",LocalDate.of(1989,5,23)),
            Person(3,"Nicole Williams",LocalDate.of(1998,8,11))
    )

    init {
        with(root) {
            tableview<Person> {
                items = persons
                column("ID",Person::id)
                column("Name", Person::name)
                column("Birthday", Person::birthday)
                column("Age",Person::age)
            }
        }
    }
}

RENDERED UI

Note that the four data properties are in fact JavaFX Properties while the age property is not. The TableView builder is smart enough to work with either pattern and handle that abstraction, even accounting for both getters and setters.

You can also specify custom cell formatters quickly by calling cellFormat() on a TableColumn. For example, we can highlight cells in red below where the Age is less than 18.

class MyView : View() {

    override val root = VBox()

    private val persons = FXCollections.observableArrayList<Person>(
            Person(1,"Samantha Stuart",LocalDate.of(1981,12,4)),
            Person(2,"Tom Marks",LocalDate.of(2001,1,23)),
            Person(3,"Stuart Gills",LocalDate.of(1989,5,23)),
            Person(3,"Nicole Williams",LocalDate.of(1998,8,11))
    )

    init {
        with(root) {
            tableview<Person> {
                items = persons
                column("ID",Person::id)
                column("Name", Person::name)
                column("Birthday", Person::birthday)
                column("Age",Person::age).cellFormat {
                    if (it < 18) {
                        style = "-fx-background-color:#8b0000; -fx-text-fill:white"
                        text = it.toString()
                    } else {
                        text = it.toString()
                    }
                }
            }
        }
    }
}

RENDERED UI

###Example 3 ####A TabPane with tabs containing GridPane layouts as well as a TableView

There are several builders for layouts including GridPane, BorderPane, VBox, and HBox. There are even builders to set up a TabePane with multiple Tab items, each containing a specified Pane.

This shows how easily we can compose complex layouts with minimal code.

class MyView : View() {

    override val root = GridPane()

    private val persons = FXCollections.observableArrayList<Person>(
            Person(1, "Samantha Stuart", LocalDate.of(1981, 12, 4)),
            Person(2, "Tom Marks", LocalDate.of(2001, 1, 23)),
            Person(3, "Stuart Gills", LocalDate.of(1989, 5, 23)),
            Person(3, "Nicole Williams", LocalDate.of(1998, 8, 11))
    )

    init {
        with(root) {
            tabpane {
                gridpaneConstraints {
                    vhGrow = Priority.ALWAYS
                }
                tab("Report", HBox()) {
                    label("Report goes here")
                }
                tab("Data", GridPane()) {
                    tableview<Person> {
                        items = persons
                        column("ID", Person::id)
                        column("Name", Person::name)
                        column("Birthday", Person::birthday)
                        column("Age", Person::age).cellFormat {
                            if (it < 18) {
                                style = "-fx-background-color:#8b0000; -fx-text-fill:white"
                                text = it.toString()
                            } else {
                                text = it.toString()
                            }
                        }
                    }
                }
            }
        }
    }
}

RENDERED UI

###Example 4 ####Menu Builders

You can also use the type-safe builders to create menus, including menus inside MenuBar or ContextMenu nodes.

class MenuView: View() {

    override val root = VBox()

    init {
        with(root) {
            menubar {
                menu("File") {
                    menu("Switch Account") {
                        menuItem("Facebook") { println("Switching to Facebook") }
                        menuItem("Twitter") { println("Switching to Twitter") }
                    }
                    separator()
                    menuItem("Save") { println("Saving") }
                    menuItem("Exit") { println("Exiting")}
                }
                menu("Edit") {
                    menuItem("Copy") { println("Copying") }
                    menuItem("Paste") { println("Pasting") }
                    separator()
                    menu("Options") {
                        menuItem("Account") { println("Launching Account Options") }
                        menuItem("Security") { println("Launching Security Options") }
                        menuItem("Appearance") { println("Launching Appearance Options") }
                    }
                }
            }
        }
    }
}

RENDERED UI

You can also specify accelerator and/or graphic node arguments for each menuitem.

menuitem("Facebook", Icons.Facebook, KeyCombination.valueOf("Shortcut+F")) {
    println("Switching to Facebook")
}

Next: Async Task Execution

Clone this wiki locally