Skip to main content


Tutorials



Bindables

Introduction

SimpleFX has its own concept and its own implementation of very, very powerful Properties. To understand the value of SimpleFX, you must understand the power of the SimpleFX properties – the so called Bindables.

In JavaFX there are about 40 different properties available. SimpleFX maps all those properties down to ONE property – called Bindable.

The beauty of the SimpleFX Bindables is that Bindables syntactically can be treated as normal variables, but, they seem almighty through their extremely powerful operators and methods. They are intuitive and amazingly simple to use.

There is no need for you to deal with the issues of weak- or strong references, when to make sure you dispose your bindings etc. SimpleFX takes care of all those issues for you.

Bindables come with a set of inherent methods, like Binding, Triggering, Imply-Statements/Invariants-support, they can memorize their entire or parts of their history, prepare the values needed for undo-implementations, they can be told to log changes etc.

When interacting with JavaFX, Bindables are used to (a) represent the standard JavaFX-properties and their standard feature-set, and (b) to offer a range of additional features coming with the SimpleFX framework.

The Import-Statement

To use Bindables, you need to import the simplefx.core, with the following statement

import simplefx.core._
import simplefx.all._

How to declare Bindables

A Bindable is a generic property, declared using the @Bind-Annotation.

Bindables are always declared as lazy properties. Therefore, the position of the declaration itself has no relevance. And therefore the sequence in which they are declared is irrelevant. The instantiation of the Bindables takes place as they are used for the first time.

Bindables are declared by means of the following syntax:

/*  Declaring a Bindable without a value  */

@Bind modifiers name:type = _

Declares a Bindable of type 'type', without a value.
Supported modifiers are the standard scala modifiers with var or val.
/*  Declaring and Initializing a Bindable  */

@Bind modifiers name[:type] = init-value

Declares a Bindable of type, with the initial value init-value.
The type is optional; it can either be explicitly provided or it is automatically inferred, based upon the type of the init-value.

Note: the init-value can also be an Expression, which in itself might contain arbitrary advanced code to be executed. This code might also contain "controllable side-effects". The only requirement to the expression is, that it must deliver a value of the type type. Such an expression is then executed once, at declaration time. See more examples below on how Expressions used here can define powerful functionality, including Bindings and Imply-Statements, beyon just returning a value of type.
/*  Declaring, Initializing and Binding a Bindable  */

@Bind modifiers name[:type] = <-- (expression)

Declares a Bindable of type type, which is bound to expression("Expression Binding"). The Bindable is invariantly set to always equal the value of expression, initially set at declaration time. The type is optional; it can either be explicitly provided or it is automatically inferred, based upon the type of the expression.

Note: the expression might contain arbitrary advanced code to be executed before it delivers an appropriate value. In other works, there are cases where it might be interesting to let it contain "controllable side-effects". The only requirement to the Expression is, it must deliver a value of the type type. The expression might contain an arbitrary number of Bindables, which are auto-detected by SimpleFX. Any time any of those Bindables are changed, the expression is executed, resulting in a new value being assigned to name.

Side-effects in Binding-Expressions is considered dangerous practice and should be used with care. This, because the execution of Bindables is sequence undependant, which means, you need a very deep understanding on how the Binding works, to know the effect of used side-effects. Therefore, side-effects in Binding-Expressions should be used for socalled "controllable side-effects", only.
/*  Declaring and Initializing a Composed Bindable  */

@Bind modifiers name[:type] = <-> (X,Y)

Declares a Composed Bindable of type type, which is bi-directionally bound to the tuple (X,Y). With this declaration, we make sure, that name and the tuple (X,Y) are always kept in sync, from the time when the declaration is executed. X and Y must be Bindables. The type is optional; it can either be explicitly provided or it is automatically inferred, based upon the type of the X and Y.

Note: a Composed Bindable can be based upon a maximum of 5 Bindables(Tuple5). The individual Bindables can be of any (individually different) type.
/*  Declaring and Initializing a Virtual Bindable  */

@Bind modifiers name[:type] = <*> (X using (
expression(X) ,
inverseExpression(name) )
)

Declares a Virtual Bindable of type type, which is bi-directionally bound to the bindable X. A Virtual Bindable has the same features as a "real" Bindable(except for the dispose-issue). It requires a "real" Bindable to be associated with, but allocates no space for the variable itself and is therefore resource-lean and a good option to using "standard" bi-directional Binding as described above. But, an important difference is, as opposed to for "real" Bindings, a Virtual Binding can NOT be disposed and therefore will exist during the Virtual Binding's entire life-cycle.
/*  Declaring and Initializing a Synonym */

@Bind modifiers name[:type] = <*> (X)

Declares a Synonym for a Bindable of type type. There are cases, in which there is beneficial to have the option of using abbreviated names (or, for whatever other reason, different names) for already existing Bindables.
/*  Declaring a Bindable with associated expression */

Any-bindable-declaration <> { expression }

Explicitly declares an "extra" expression to be executed once at declaration time. A "standard" Bindable-declaration and the expression to be associated with it are delimited with the <>. This expression makes room for a magnitude of interesting patterns to be used for different scenarios. See the Examples below for some of them.


Some examples of Bindable-declarations


/*  Declaring a Bindable with no value  */

@Bind var a:Double = _

Declares a Bindable of type Double, not yet provided a value.
/*  Declaring and Initializing a Bindable  */

@Bind var a = 1

Declares a Bindable of type Int, with the initial value set to 1.
/*  Declaring, Initializing and Binding a Bindable  */

@Bind var b:Int = <-- (a + 1)

A Bindable of Int, which is bound to the expression a + 1

.
/*  Declaring, Initializing and Binding a Bindable  */

@Bind var b:Int = <-- { .. this code is being executed every .. .. time a Bindable inside of this expression changes .. .. returns a value of type Int ..
}

A Bindable of Int, which is bound to an Expression. SimpleFX auto-detects any Bindable being a part of the expression and makes sure that any change of any of those Bindables cause a re-execution of the expression.
Here, it is also possible to associate "controllable side-effects" with any re-calc of the expression.
/*  Declaring and Initializing a Composed Bindable  */

@Bind var transXYZ = <-> (translateX, translateY, translateZ)

A Bindable, called transXYZ, being bi-directionally bound to the Tuple3, consisting of the 3 Bindables translateX, translateY and translateZ.
/*  Declaring and Initializing a Virtual Bindable  */

@Bind var end = <*> (start using ( start + expansion , _ - expansion )
)

A Virtual Bindable, called end, being bi-directionally bound to a Bindable, called start, as often needed for a Shape-instantiation. The first expression-parameter represents a function, which returns the value to be assigned to 'end' as any of the Bindables start or expansion are changed. The second expression-parameter represents a function, which returns the value to be assigned to start as any of the Bindables end or expansion are changed. The underscore represents the end.
/*  Declaring and Initializing a Synonym  */

@Bind var laX = <*> (layoutX)

A Synonym for the Bindable layoutX.
/*  Declaring a Bindable with associated expression */

@Bind var name = <-- (a + 1) <> { println("Here the 'name' is declared. It is all lazy, ") println("so, clearly, someone is using 'name' now ... ") var count = 0 name --> { count += 1 println("Change number " + count + " ... to: " + name ) }
}

A simple expression, containing just some print-statements. The value of name is printed every time name changes. The first two print-statements are executed just once, initially, whereas the third one is executed for all changes of name.

Note: because a @Bind-declaration is always lazy, the complete declaration is lazy; as opposed to the following declaration:

  @Bind var nonLazy = <-- (a + 1)
name --> println("...")

in which the non-lazy trigger-declaration --> makes the declaration of nonLazy de facto non-lazy.

Bindable Operators

The Bindables come with a set of very powerful methods and operators. The most importan ones to learn about are those required for the Expression-Binding, Triggering and Progressive Assignments. Most of the operators described here are used and described in the chapter How to declare Bindables above, as well.

/*  Assigning a value to a Bindable  */

bindable = (expression)

Assigns the resulting value of expression to the Bindable called bindable. The value assigned is the result of the expression, which is of the generic type of the Bindable (it is NOT a reference to the Bindable itself).
/*  Binding an expression to a Bindable  */

bindable <-- (expression)

Binds the expression to bindable. The first assignment to bindable is executed immediately, meaning, the Binding is valid from this point on. It declares an invariant for bindable.
/*  Binding a Bindable to a Bindable  */

bindable <-> anotherBindable

Establishes a Bi-directional Binding between the bindable and the anotherBindable.
/*  Binding a Composed Bindable to a Tuple  */

bindable <-> (bindable1, ... ,bindableN)

Establishes a Bi-directional Binding between the bindable and the Tuple of bindables on the right side. The maximum number of bindables supported is 5.
But, for this Binding-definition, the type of bindable is already defined and must, of course, match the Tuple provided.
/*  Binding two Bindables Bi-Directionally  */

bindable <-> (anotherBindable using ( expression(anotherBindable) , inverseExpression(bindable) )
)

Establishes a Bi-directional Binding between the bindable and the anotherBindable.
The expression(anotherBindable) is a function, which takes anotherBindable as parameter and calculates the new value of bindable each time anotherBindable has changed.
inverseExpression(bindable) is a function, which takes bindable as parameter and maps any change to bindable into a new anotherBindable-value.
SimpleFX makes a simple validation check, which would cause an exception, should the expression(anotherBindable) and the inverseExpression(bindable) not represent the inverse-function of each other.
With this declaration, we make sure, that bindable and anotherBindable are always kept in sync, according to the defined expressions(functions/lamdas), from the time when the declaration is executed.
The type is optional; it can either be explicitly provided or it is automatically inferred, based upon the type of the expression(anotherBindable).
The inverseExpression(name) must obviously be of the same type as anotherBindable. Of course, anotherBindable can also be a Composed Bindable.
/*  Assignment-Triggering on Bindable-changes   */

bindable --> anotherBindable

Triggers the execution of anotherBindable being assigned the value of bindable every time bindable changes, including at initial declaration.
/*  Function-Triggering on Bindable-changes   */

bindable --> function

Triggers the execution of function every time bindable changes, including at initial declaration. In other words, the first trigger is executed immediately.
/*  Imply-Definition on Bindable-changes   */

bindable ==> function

Triggers the execution of function every time bindable changes, including at initial declaration. In other words, the first trigger is executed immediately.
When the Imply-Operator ==> is used, as opposed to the Trigger-Operator-->, SimpleFX automatically takes care of the "cleanup" of any Disposer being created inside of function. This means, any Trigger-, Binding-, Imply-, Progressive Assignment-Operator etc. used inside of the last execution of function, is automatically disposed before the next function-execution takes place.
/*  Surpressing execution at Declaration time   */

-->* // Trigger on future changes, only.
==>* // Trigger on future changes, only.

Some times there is a need of using the Bindable's Operators in a way, which slightly deviate from the standard behaviour. Therefore, many of the operators are offered in a slightly modified version - with an asterisk * attached to them - indicating, that no execution should take place at Declaration time. Execution should take place for any change happening post Declaration time, only.

The when-statement

The when-statement is used in association with Boolean Conditions, in order to define under which conditions certain functions are to be executed.

/*  when (condition) trigger   */

when (condition) --> { function }

Whenever the condition is set to true, execute the function.

At declaration time, the actual value of condition decides whether function should be initially executed or not.

The execution of function depends on the value of condition, only. And, no matter when condition turns from false to true, the function is always triggered.

At declaration time, the actual value of condition decides whether function should be initially executed or not.

The execution of function depends on the value of condition, only. And, no matter when condition turns from false to true, the function is always triggered(as long as it has not been triggered, already). When condition turns from true to false, nothing happens. Just as when condition being set from true to true, also nothing happens.
/*  when (condition) imply   */

when (condition) ==> { function }

Whenever the condition is set to true, execute the function, and
Whenever the condition is set to false, dispose all Disposers.

At declaration time, the actual value of condition decides whether function should be initially executed or not.

The execution of function depends on the value of condition, only. And, no matter when condition turns from false to true, the function is always triggered.

The Imply-statement has the nice ability of "cleaning up" whatever it has created before, as soon as the condition turns false. This means, any Disposer created inside of function is disposed as soon as the condition turns false. In other words, whenever false, all Bindings, Triggers, Imply-statements, Progressive Assignments etc. from the last execution are automatically un-done.
/*  onceWhen (condition) trigger   */

onceWhen (condition) --> { function }

Whenever the condition is set to true, execute the function.
But, do it a maximum of ONE time.

At declaration time, the actual value of condition decides whether function should be initially executed or not.

The execution of function depends on the value of condition, only. And, no matter when condition turns from false to true, the function is always triggered(as long as it has not been triggered, already). When condition turns from true to false, nothing happens. Just as when condition being set from true to true, also nothing happens.

/*  onceWhen (condition) imply   */

onceWhen (condition) ==> { function }

Whenever the condition is set to true, execute the function, and
Whenever the condition is set to false, dispose all Disposers.
But, a function-execution can only happen ONE time.

At declaration time, the actual value of condition decides whether function should be initially executed or not.

The execution of function depends on the value of condition, only. And, no matter when condition turns from false to true, the function is always triggered.

The Imply-statement has the nice ability of "cleaning up" whatever it has created before, as soon as the condition turns false. This means, any Disposer created inside of function is disposed as soon as the condition turns false. In other words, whenever false, all Bindings, Triggers, Imply-statements, Progressive Assignments etc. from the last execution are automatically un-done.

Event-Declarations

Event-declarations are used to define certain "trigger-points" (or Events) for functions to be executed (or triggered).

Note: SimpleFX supports Event-expressions, like (event && condition), which are themselves Events. This opens up for more advanced definitions to be used as criterias for the function-execution.

When an Event-expression - an Event combinded with a condition - is used, it is important to know that, should the event emit when a condition is false, then a subsequent condition changing from false to true will NOT cause the Event-expression to emit. Only, if the condition is true as the event emits, the Event-expression will emit(and thereby cause a trigger).

/*  A Trigger-Event    */

event --> { function }

Whenever the event emits, execute the function.

At declaration time, no triggering happens, meaning the function is NOT initially executed.

/*  An Imply-Event    */

event ==> { function }

Whenever the event emits, execute the function.

At declaration time, no triggering happens, meaning the function is NOT initially executed.

Whenever the function is to be executed, in advance, all Disposers created during the last execution of function are disposed. In other words, whenever re-triggered, all Bindings, Triggers, Imply-statements, Progressive Assignments etc. from the last trigger are automatically un-done.

/*  A singular Trigger-Event    */

once(event) --> { function }

Whenever the event emits, execute the function. But, do it a maximum of ONE time.

At declaration time, no triggering happens, meaning the function is NOT initially executed.

/*  A singular Imply-Event    */

once(event) ==> { function }

Whenever the event emits, execute the function. But, do it a maximum of ONE time.

At declaration time, no triggering happens, meaning the function is NOT initially executed.



Event-Generating Methods

In order to utilize the strong capabilities of the Event-Statements above (See "Event-Declarations") SimpleFX comes with a set of methods, which by themselves generate Events.

/*  The in-Method  */

in(duration)

This method generates an Event after duration, relative to the time of declaration. Used in an Event-Statement it could look like this

    in(200 ms) --> { function }
/*  The at-Method  */

at(time)

This method generates an Event at time. The given time must be in the future. Used in an Event-Statement it could look like this

    at(time + (10 s)) --> { function }
/*  The nextFrame-Method  */

nextFrame

nextFrame(step)

This method generates an Event as soon as the next frame is timely reached, relative to the time of declaration. When a step is provided, the next frame is defined as the frame number step, relative to current frame. Used in an Event-Statement it could look like this

    nextFrame ==> { function } or nextFrame(10) ==> { function }
/*  The everyFrame-Method  */

everyFrame

everyFrame(step)

This method generates an Event whenever a next frame is timely reached, starting at the time of declaration. When a step is provided, the next frame is defined as the frame number step, relative to the last event-generating frame. Used in an Event-Statement it could look like this

    everyFrame ==> { function } or everyFrame(3) ==> { function }
/*  The every-Method  */

every(duration)

This method generates Events in an intervall of duration, starting at the time of declaration. Used in an Event-Statement it could look like this

    every(1 s) --> { function }


Core Methods

In general, there exist some particularly important and central methods, which should be known and understood, in order to write efficient SimpleFX programs.

/*  The updated-Method  */

updated { expression }

This method can almost be considered The Constructor-Method for classes defined in SimpleFX. It should be used in almost all cases where you create SimpleFX-classes containing public properties. When we here say properties, we basically mean Bindables. But, it also yields for JavaFX-Properties, should you explicitly use them directly, for some reason. See here for details on recommended patterns for SimpleFX-classes.

The update-method is used for packing the business-logic of a class into an environment in which the update and the use of all properties are guaranteed to happen at the right time and in the right sequence. For public properties this would not be given otherwise. Putting the expression inside of the update-method gives you the garantuee, that the business-logic is executed AFTER all public properties have been updated. Would you use public properties outside of the update-method, then you would run the risk, that your properties do not have the correct values before you start using them.
/*  The inFX-Method  */

inFX { expression }

This method is used to inject a portion of code (the expression) into the JavaFX-Application-Thread . In other words, if you have a thread - different from the JavaFX-Application-Thread - from which you would like to modify the Scene-Graph, you need to use the inFX-method, such that the code for the modification takes place in the appropriate thread - the JavaFX-Application-Thread.
/*  The runLater-Method  */

runLater { expression }

This method is often used and needed in situations in which native JavaFX-code(no SimpleFX) is being used. Sometimes, JavaFX programs need to package expressions into a runLater-statement, in order to trigger Scene-Graph-updates to take place in the correct sequence. For SimpleFX programs this method should NOT be needed.


Garbage Collector Semantics for Bindings

In order to offer a convenient use of Bindings, we incorporated a set of validation algorithms and mechanisms necessary to avoid ugly problems like memory-leaks to happen when used in an unproper way. We made sure that memory is released in an intuitive manner. If we look at a very simple case of Binding, like the following ..

a <-- (b + c)

.. then, SimpleFX's semantics let's the Binding stay alive as long as there exists a reference to a. And, b and c are not collected as long as a is in use. In other words, a prevents b and c from being collected, whereas b and c, on the other hand, do not prevent a from the same.

This is the basic principle of the garbage collector semantics for SimpleFX's Binding, and it is complient with the behaviour which developers would normally expect.

It is important to note, that this semantics is different from the classical observer pattern or the binding implementation of many other solutions, like the embedded Binding-concept of JavaFX, for instance.

With the observer pattern, b would reference a from being collected. The observer pattern "solves" this problem, by handing the responsibility of correct disposing (avoiding memory-leaks) over to the developer. In other words, it becomes the programmer's responsibility to manually dispose at the right time. This is time-consuming and error-prone. With the SimpleFX Binding, the programmer does not need to care.


Interaction with JavaFX

How to create nodes

In SimpleFX the instantiation of objects can be done in one atomic action. The JSON-inspired, object-literal syntax makes it easy and "natural" to define Expression-Bindings or Assignments for individual properties in a unified and consequent manner. This inherent mechanism of SimpleFX is therefore used also when creating JavaFX Nodes.
SimpleFXJavaFX
val rec = new Rectangle {
	fill <-- (if (hover) Color.BLUE else Color.GREEN)
	xy     = (20,20)
	wh   <-- (xy * 2 + (30,30))
}
Rectangle rec = new Rectangle();
rec.fillProperty()
   .bind(new When(hoverProperty())
   .then(Color.BLUE)
   .otherwise(Color.GREEN));
rec.setX(20);
rec.setY(20);
rec.widthProperty()
   .bind(xProperty().multiply(2).plus(30));
rec.heightPropert()
   .bind(yProperty().multiply(2).plus(30));
Note, in the SimpleFX version the definition of the instantiation itself is clearly defined; as opposed to in the Java-code, in which a list of subsequent statements is required. The SimpleFX code, even without the use of Bindings, is easier to read and more clearly expresses what is the actual task in this case - to instantiate an object with a certain set of properties.

SimpleFX also opens up for the use of more abstract datatypes, like points, vectors, time, etc. And, allthough the SimpleFX defines Binding Expressions for fill and wh - and not just simple Assignments - the size of the code is not affected.



Time syntax



Progressive Assignments