Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Monday, March 27, 2017

Kotlin Elements in TorandoFX Type Safe Builders

While the Kotlin-based TornadoFX framework supports many styles to construct a JavaFX UI, the Type Safe Builders are preferred.  Borrowing from a popular Groovy syntax, the TornadoFX Type Safe Builders are Kotlin Extension Functions that use Lambdas to write cleaner programs.

This is an example TornadoFX View which is an HBox containing three Labels.

class GridDemosMainView : View() {

    override val root = hbox {

        label("Label 1")
        label("Label 2")
        label("Label 3")
    }
}

Lambda Last Param


hbox() is a function that uses a special feature of Kotlin whereby the last argument of a function can be written outside of the parenthesis if it is a Lambda.  Search for "last parameter" in the Kotlin section "Higher Order Functions and Lambdas".

The following function demonstrates this.

fun myfun( op: (() -> Unit)? = null) {
    println("MYFUN")
    op?.invoke()
}

Just like the TornadoFX Type Safe Builders, myfun() takes a nullable no-argument Lambda as a terminal parameter.  myfun() prints "MYFUN" and then calls the Lambda if one was provided.  invoke() is a method of the Lambda's Function object.

As an aside, if op were not nullable, the following would be legal. op() is shortened for op.invoke().

fun myfun2( op: (()->Unit) ) { op() } // not-null

Getting back to the nullable myfun(), the following calls are supported.  These will be familiar to Java developers.  A Lambda is provided as a named argument "op".  The name can be omitted.
    
    myfun( op = { println("extra code") })

    myfun( { println("extra code") })  // named arg not needed

Running either of these will produce a line "MYFUN" from the function itself and "extra code" from the Lambda.

Alternatively, myfun can be called using curly braces at the end of the method.  This is the style most commonly seen in TornadoFX because it does not require extra the added nesting as a method parameter.

    myfun {
        println("extra code - cleaner")
    }

Running this will output "MYFUN" and the message "extra code - cleaner".

Extension Function Receiver


The block provided to a layout component like an HBox does more than invoke a sequence of operations.  The block also adds child components.  To do this, TornadoFX uses an Extension Function Receiver to add the child nodes to the parent layout container.

This sample Kotlin code demonstrates the technique.  Container simulates a JavaFX layout container like HBox.  Part simulates a JavaFX control like a Button or Label.  "Builder" is a base class that is extended with a pair of factory functions: buildContainer and buildPart.

open class Builder

class Container(val containerName: String,
                val parts : MutableList<Part> = mutableListOf()) : Builder()

class Part(val partId: String) : Builder()

fun Builder.buildContainer(name : String, 
         op: (Container.()->Unit)? = null ) : Container {
    val container = Container(name)
    op?.invoke(container)
    return container
}

fun Builder.buildPart(id : String) : Part{
    val part = Part(id)
    (this as Container).parts.add( part )
    return part
}

buildContainer() has a terminal parameter that is a Lambda, so callers can use the code block syntax described in the previous section.  buildContainer() creates a Container object and returns this object to the caller.

buildPart() constructs and returns a Part object.  However, prior to returning the value, the part is added to a parent Container's list of children.  "this" in buildPart is a receiver object.  The receiver object is the Container instance and accessing its collection field adds the Part object.  "this" is actually of type Builder in accordance with the extension function, but we know that it can only be a Container in this demonstration.

This is a code snippet that shows the Container and Part builders mirroring what might be one with a TornadoFX hbox{} / label{} pair.

    val c = Builder().buildContainer("A") {
        buildPart("1")
        buildPart("2")
        buildPart("3")
    }

    println("container name=${c.containerName}")
    println("# items c=${c.parts.size}")
    println("\titem[0]=${c.parts[0].partId}")
    println("\titem[1]=${c.parts[1].partId}")
    println("\titem[2]=${c.parts[2].partId}")

Running the program outputs the containerName and partId fields.

/opt/jdk1.8.0_121/bin/java ...
...
container name=A
# items c=3
 item[0]=1
 item[1]=2
 item[2]=3

Process finished with exit code 0

This example presented two features of the Kotlin programming language used by the TornadoFX framework: Terminal Lambda Parameter and Extension Function Receiver.  The nesting of the original hbox{} / label{} example is a clutter-free example of a UI constructed in TornadoFX.  With the syntax pared down, the program is set up to manage additional complexity through a composed solution.  For the a complex app, controls would be arranged in panels and assembled into using higher-order composite structures like Fragments.

No comments:

Post a Comment