Exit Full View

Glok / glok-demos / src / main / kotlin / uk / co / nickthecoder / glok / demos / DemoFormGrid.kt

package uk.co.nickthecoder.glok.demos

import uk.co.nickthecoder.glok.application.Application
import uk.co.nickthecoder.glok.application.launch
import uk.co.nickthecoder.glok.control.FormGrid
import uk.co.nickthecoder.glok.control.FormRow
import uk.co.nickthecoder.glok.control.Label
import uk.co.nickthecoder.glok.control.ScrollPane
import uk.co.nickthecoder.glok.demos.DemoFormGrid.Companion.ABOUT
import uk.co.nickthecoder.glok.property.boilerplate.ObservableBoolean
import uk.co.nickthecoder.glok.property.functions.isEmpty
import uk.co.nickthecoder.glok.property.functions.not
import uk.co.nickthecoder.glok.scene.Edges
import uk.co.nickthecoder.glok.scene.Stage
import uk.co.nickthecoder.glok.scene.dsl.*
import uk.co.nickthecoder.glok.theme.styles.ERROR

/**
 * See [ABOUT].
 */
class DemoFormGrid : Application() {

    lateinit var form: FormGrid

    override fun start(primaryStage: Stage) {
        with(primaryStage) {
            title = "DemoFormGrid"

            scene {
                root = demoPanel {
                    name = "DemoFormGrid"
                    about = ABOUT
                    relatedDemos.addAll(DemoTantalum::class, DemoFormGridJoined::class)

                    content = borderPane {
                        center = scrollPane {
                            fitToHeight = true
                            fitToWidth = true

                            content = form {
                                section = true
                                form = this
                                padding = Edges(10f)
                                rowSpacing = 20f

                                + row { // See the next row for a "better" way to set the left label.
                                    left = Label("Name")
                                    right = textField("") { // Just here to demo promptText
                                        prefColumnCount =
                                            20 // By setting growPriority, this will extend to the right of the FormGrid
                                        growPriority = 1f // Adds a validation check to the row.
                                        errors = errorMessage(
                                            "Required",
                                            textProperty.isEmpty()
                                        )
                                        // Note `errors` is deliberately set within the `textField` block of code.
                                        // In this block, we have many "receivers". a TextArea, a FormRow, a GridForm...
                                        // The line above could be written more verbosely as :
                                        //
                                        //     this@row.errors = this@DemoFormGrid.errorMessage("Required", this.textProperty.isEmpty())
                                        //
                                        // Which highlights the 3 different receivers being used.
                                    }
                                }

                                // As `left` is very often a Label, there's a convenience function for this case :
                                + row("Phone Number") {
                                    right = textField("") {
                                        // Note, we have asked for the same width as "Name", but have not set growPriority.
                                        // Resize the window to see the difference.
                                        prefColumnCount = 20
                                    }
                                }

                                + row("Click to agree") {
                                    above = Label("I have read, and agree with the terms and conditions")
                                    right = checkBox {
                                        errors =
                                            errorMessage("You must agree before before continuing", ! selectedProperty)
                                    }
                                }
                            }
                        }

                        bottom = hBox {
                            padding(4)

                            + spacer()
                            + button("OK") {
                                onAction {
                                    val errorRow: FormRow? = form.rows.firstOrNull { it.errors?.visible == true }
                                    if (errorRow == null) { // Validation passed, so update the database???
                                        scene?.stage?.close()
                                    } else {
                                        // Look for a ScrollPane (if there is one), and ensure the error row is visible.
                                        // Using grandparent, because the form's parent is the ScrollPane's private Viewport node.
                                        val grandparent = form.parent?.parent
                                        if (grandparent is ScrollPane) {
                                            grandparent.scrollTo(errorRow)
                                        }
                                        // This will focus on the TextArea that is invalid.
                                        errorRow.requestFocus(useFocusTraversable = true, showFocusBorder = true)
                                    }
                                }
                            }
                        }
                    }
                }
            }
            show()
        }

    }

    private fun errorMessage(text: String, error: ObservableBoolean) =
        label(text) {
            style(ERROR)
            visibleProperty.bindTo(error)
        }

    companion object {

        private val ABOUT = """
            [FormGrid] is specifically designed to make laying out forms easy,
            much easier than using [GridPane]. I hate using JavaFX's [GridPane].
            
            A [FromGrid] has a list of rows ([FormRow]), and each row has up to 4 nodes :
                `left`   : Usually used for a [Label]
                `right`  : Usually used for a [TextField], [Spinner], [CheckBox], etc.)
                `above`  : Less frequently used. Can be used instead of `left` and `right`.
                `below`  : Information about the row.
                `errors` : To display error messages when the input is invalid.
                
            The `right` columns line up with each other.
            `above`, `below` and `errors` take up the full width of the row, and do not affect the
            position of the `right` Nodes.
            
            PS. There's another reason not to use [GridPane], I haven't written it yet! ;-))
            
            This demo includes simple validation checks.
            Rows which require validation set the row's `errors` content to a Label,
            which is automatically made visible/invisible based on an ObservableValue.
            
            The +OK+ button only needs to check if any row's `errors` content is visible.
            If it is, do _NOT_ process the form, and instead, requestFocus on the `right` of the row.
            Note, this functionality is not part of [FormGrid] or [FormRow], and you can
            implement a more comprehensive validation system if you want.
            """.trimIndent()

        @JvmStatic
        fun main(vararg args: String) {
            launch(DemoFormGrid::class)
        }
    }

}