PropertyFunctions

This interface is NOT USED, it is only here for Documentation.

A Property Function takes one or more ObservableValues, and returns another ObservableValue. A simple example :

val a = SimpleIntProperty(1)
val b = SimpleIntProperty(2)
val c : ObservableInt = a + b

Note, c is NOT 3, it's value is 3 at the moment, but as a or b change, so will c.

I highly recommend using Property Functions extensively. IMHO, your application will be cleaner, less buggy and easier to understand.

Glok has many built-in Property Functions in the uk.co.nickthecoder.glok.property.functions package. But it's quite easy to build your own.

There's 3 base classes : UnaryFunction, BinaryFunction and TernaryFunction.

fun ObservableInt.plus1() = UnaryFunction(this) { v -> v + 1 }
fun average(oa: ObservableInt, ob: ObservableInt) = BinaryFunction(oa, ob) { a, b -> (a + b) / 2 }

If you need more than 3 parameters, you can use addDependent().

fun sum4(oa: ObservableInt, ob: ObservableInt, oc: ObservableInt, od: ObservableInt) =
    TernaryFunction(oa, ob, oc) { a, b, c -> a + b + c + od.value }.apply { addDependent(od) }

A little ugly (asymmetric), but it gets the job done! If you prefer, you could use the base class LazyObservableValue instead.

The examples above, are BAD! The returned values are not ObservableInt, they are ObservableValue. See Boilerplate for a longer description why using the generic property types are, IMHO, bad.

Let's fix them :

fun ObservableInt.plus1() = IntUnaryFunction(this) { v -> v + 1 }
fun average(oa: ObservableInt, ob: ObservableInt) = IntBinaryFunction(oa, ob) { a, b -> (a + b) / 2 }

If the input parameters are nullable (glok uses the term Optional) then :

fun ObservableOptionalInt.plus1() = OptionalIntUnaryFunction(this) { v -> if (v == null) null else v + 1 }

The Hidden Power

These examples may seem a little naff, and not very important, so let's see another example, which I hope will change the way you code...

fun personDetails(observablePerson: ObservableValue) = NodeUnaryFunction(observablePerson) { person ->
    formGrid {
        if (person != null) {
            row("Name") {
                right = Label(person.name)
            }
            row("Age") {
                right = Label(person.age.toString())
            }
        }
    }
}

It creates an ObservableNode, whose value changes whenever observablePerson changes. We can then add this node to our scene :

val personDetails = singleContainer{
    contentProperty = personDetails( myListView.selection.selectedItemProperty )
}

and the content will update whenever the list's selected item changes. Note, we could create another property-function which uses TextField and IntSpinner whose values are bound to the properties on Person :

fun editPersonDetails(observablePerson: ObservableValue) = NodeUnaryFunction(observablePerson) { person ->
    formGrid {
        if (person != null) {
            row("Name") {
                right = textArea(person.name) {
                    textProperty.bidirectionalBind( person.nameProperty )
                }
            }
            row("Age") {
                right = intSpinner(person.age) {
                    valueProperty.bidirectionalBind( person.ageProperty )
                }
            }
        }
    }
}

Our code is declarative, rather than imperative, with some advantages of a pure functional language.