What is a binding?
It’s nice that we can create changes in Groovy objects that can be observed by an interested party, but that is only half the story. Really, we want to do some cool stuff with these changes, and what would be really magical is if we could do some of these things automatically. In this segment, you’ll do just that when you learn how to bind one value to some other value.
What is a binding? In Griffon a binding has three constituent parts: a trigger, a read, and a write. The trigger tells the binding that it needs to update, the read tells the binding what the new value is, and the write takes the new value and does something with it.
|This article is based on Griffon In Action, published November, 2009. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book’s page for more information.
Developer Tutorial readers can get 30% off any version (ebook or print book) of Griffon In Action. Simply use the code “devtut30” at checkout.
JavaBeans bound properties provide the simplest manifestation of a binding (table 1).
Table 1: A binding from a JavaBean’s point of view
|Trigger||The property getting changed|
|Read||The source is reading the value of the property|
|Write||The target is storing the new property value in the variable|
However, we can make any of the three pieces of a binding as complex as we need to make them. In fact, as you write a Griffon application you are probably using some rather complex binding mojo without even knowing it. That’s the point.
A basic binding call
The SwingBuilder class in Groovy contains more than just visual widgets; it also contains helper nodes that are useful in building GUI applications. One of those is the bind node that allows us to cleanly bind the results of two objects together. As we will discuss later, it can bind both bound and unbound properties. Let’s start with the simple tricks first. Listing 1 shows how to bind the field bar on
obj to be the same value as
Replace #1-#6 in the following paragraph with cueballs
Being an all-in-one example, there is a bit of ceremony that we need to take care of that you normally wouldn’t need to do or that you would do differently if you follow the file layout. First, you need to import the SwingBuilder and instantiate an instance of the SwingBuilder #1. Normally we wouldn’t need to do this but because this is a standalone script we have to do it explicitly. The next step is something that we would normally do in a model class: we declare the class and the observable (or bindable if you will) attribute #2.
At last, all of the ceremony is over and we can get to the meat of the example. We now bind the field bar on
obj to be the same value as
foo #3. This binding takes effect instantly, thus when we check the value of bar we see that it is now ‘One’, which was the value the field foo had when we instantiated the object #4. When we change the value of the field
foo #5, the change is also reflected in the field
bar #6 which is now
'Three' as well #6.
The several flavors of binding
There is more than one way to call the bind method. What is happening is that we are expressing the essence of the binding and extracting the needed ceremony from the context of the bind all. But just like long division, before we can understand the essence of the binding, let’s examine a few fully expressed binding calls. There are three basic flavors:
- By and large the most common flavor of binding is a property-to-property binding.
- A less common but equally valuable flavor is to separate out the trigger from the read.
- The last flavor to fully declare a binding is a hybrid of the previous two forms.
Source and target property binding
1. Trigger and Read
In this case the read and the trigger are expressed as the same thing, a bound property on a particular object instance. The instance that will provide both the change notification and the changed values is declared in the source: attribute. The property that will both provide the value and provide the trigger when it is changed is declared in the sourceProperty: attribute. Both of these attributes constitute the trigger and the read. The location we are going to write the value to is also declared in two parallel values, the target: attribute and the targetProperty: attribute. Both of these properties together constitute the write.
Event trigger, closure read, and property write binding
Separating out the trigger from the read is usually done when the property providing the read value is not observable, but there are other events in the widget that may notify us that states need to be updated. One classic example is JTextComponents.
The text property of the
JTextComponent and its many subclasses does not provide a means to directly notify us when the value has changed, primarily because it is not directly backed by a field but is actually the result of calculations done against the
Document object backing the text field. The
Document object does provide events that we can latch onto to detect changes. To declare a non-property event trigger we need to declare a
sourceEvent attribute in the bind call.
In addition to binding to an event we can bind to values that are not directly properties. For example, if we are updating an undo button the trigger is the document manager but the actual value is managed by a third-party
UndoManager instance. In this case we would need to declare a
sourceValue: element, and pass in a closure that will provide the value to be read . This attribute is entirely independent of any other attributes.
Targets, on the other hand, are not provided much of the flexibility that sources and triggers receive. Both the
targetProperty: attributes are needed to determine where to write the value to. If you need to do fancy stuff, you can convert the value to something else.
Source event and property, target property
There is one last way to fully declare a binding, and it is a hybrid of the previous two. It is not nearly as common as the previous two methods and is mentioned for completeness. We can trigger off an arbitrary event on an object and also read the property from the same object.
source: attribute can be shared between a
sourceEvent: and a
sourceProperty: element. In fact, they must share the same source if both attributes are declared. If you need to have different sources you are better served by using the
sourceValue: attribute and expressing the read as a closure. Finally, the write is expressed by the
target: bean and the
That covers the three basic forms of a fully expressed binding. However, there are ways to tease out the essence from the required ceremony.
Finding the essence
There are three ways to extract the essence of the binding from the full ceremonial declaration of the bind node:
- We can provide the source property and target property as an unnamed argument.
- We can imply source and target property bindings by using the bind() node as part of another SwingBuilder node.
- We can express the trigger and read as a closure.
Implicit argument property
Nodes can take arguments and attributes. Arguments don’t have a label while attributes do. When a bind node is passed an argument that is a string, as in listing 2, the argument value is presumed to be the value for
targetProperty: attributes if those attributes are not passed in and they are needed.
The first three nodes in the example are just reworked versions of the prior three examples, except that in the first and the third nodes the sourceProperty: attribute is implied by the argument value, and in the second node it is the targetProperty: attribute that is implied. Note that the argument value can also imply both the sourceProperty: and targetProperty: attributes, as shown in the fourth node. The result of the fourth bind node would be that the state of the secondCheckbox selection would be driven by the first checkbox.
Contextual property bindings
Another instance in which we can glean the essence of the ceremony is when the bind node is constructed within the context of a view script and the bind node provides the declared value of the attribute. In this case, the object that the node is creating becomes either a source or target object, and the attribute becomes the property for the implied portion of the binding. The four nodes shown in listing 3 are semantically identical to the four nodes we looked at in listing 2 (implicit argument examples), except that we have recast them to be written as properties in the declaration of the visual nodes.
What is notable about these examples is that half of the contextual properties result in the source being represented by the context the other half result in the target being represented by the binding. How does Griffon know where to use the context? It looks at what is already explicitly provided and then provides the rest from the context. But what happens if you provide both the source and the target in the binding but still do the
bind() node as the value to an attribute? The context of the bean is then not used to calculate the binding and instead the attribute is set with a BindingUpdateable object, which stores the realization of the binding.
Peeking behind the curtain
How does the
bind() node deal with contextual properties? The bind node handles it in part by using a stand-in object and using an attribute delegate in the
FactoryBuilderSupport to finish the processing.
Semantically the bind node is evaluated before the parent object is calculated. At the time that the bind node itself is processed it does not have any way to get at the contextual property. However the BindFactory does whatever work it can with the explicit portion of the node and then returns the half built objects to stand in for its fully bound state.
Once the parent node starts processing the
FactoryBuilderSupport allows registered attribute delegates a chance to post-process the attribute values before it applies them as properties to the resulting object. The BindFactory registers a delegate that will identify the stand-in object and finish the processing of the binding with the name of the attribute and the instance of the object being constructed.
Binding to a closure
The final way to extract the essence of the binding from the full ceremonial declaration of the bind node is to use a closure to express the trigger and read values as a closure containing the values to be queried. This is by far the most concise and expressive way to declare a binding in Groovy or Griffon. Listing 4 shows a binding closure.
You will note that only two of the samples from the previous set of examples have been repeated. Binding to a closure is not possible for all scenarios. A binding closure can only be used to express the source and the source values. If a binding needs to trigger off of a non-property event or the binding gets its source context from the node, then a closure binding cannot be used.
The closure binding is deceptive in its simplicity and power. The first two examples show a very simple closure binding: a single object reading a single property value. The third example shows a more powerful use: multiple properties being processed into a new value. It is in this example that we can see that possibly the closure binding represents a fourth form of a fully expressed binding: one that triggers an update when one of several distinct properties determined at runtime are changed.
Peeking further behind the curtain
The ability to trigger off of multiple properties is mostly a side effect of how the closure binding is implemented. Two language aspects of Groovy combine to allow for the closure binding to work. The first is the fully dynamic nature of each of its method invocations. When a method is invoked in Groovy the invocation is passed through the meta-class to allow it to provide alternate options dynamically at runtime. This is what makes a dynamic language dynamic: the presence of some sort of a Meta Object Protocol.
The second aspect of Groovy that allows the closure binding to work is the use of a delegate on the closure object. Each closure is represented by a distinct Java object, and a delegate can be assigned to that object. The unbound variables in the closure are then resolved against either the object the closure was declared in or the delegate object.
The closure binding uses both of these features to get a listing of the objects and properties that will be inspected for operability. When the binding instance is bound the properties that whose changes can be observed are listened to for changes. When any of the properties change then the value of the closure is evaluated, the target is updated, and if needed new properties have listeners attached.
Other blending options
Beyond the basic requirements of the binding, there are still some other options that can be wired into the binding that affect the processing of a binding update. The binding can convert values being read into other values and it can also validate values and keep invalid values from being passed on to the target. When using a binding, one is not stuck with a one-to-one, take-it-or-leave-it update, values can be adjusted and even rejected. Extra attributes can be added to the bind node to provide this added functionality. There are also some corner cases relating to some uses of the bind node that can be resolved by additional attributes.
Converting values read from a binding
One common requirement is to translate one value into another value. A common situation is for a data field to have a public name such as ‘Red’, ‘Green’, or ‘Blue’ while the data model may require these values to be stored as integers:
0xff0000, 0x00ff00, 0x0000ff respectively. This is clearly a task that should be done as closely to the view as possible, to maximize the time that the data can be stored in its preferred format. To do this the
converter: attribute is passed into the binding arguments and it is provided a closure. This closure is given the value obtained from the read of the binding and the result of the closure is passed into the write of the binding. In listing 5, we are mapping the common names for the colors with the internal AWT object representing those colors.
The issue is that the user expects to see the String names, but the label wants a color object. The closure we provided simply takes the string value and maps it to the regular value.
But what about instances where the bound values may not always be translatable to a model value? How do we ensure that only the valid values get written to the target? Enter the
Validating values read from a binding
The validator closure will be called, and if it returns a Boolean value of
true, the binding update continues. If the validator returns any other value, then the binding update is silently stopped and the value read from the source will neither be converted nor written to the target. In listing 6, we change things up slightly from the previous incarnation. Instead of a combo box that limits the user’s entries, we make the user type a valid color name into a text field.
To ensure that we only try to change the color on valid values, we add a validator closure that checks to see if the color in the text field exists in the
colors map. If it doesn’t exist, then it is as if the binding doesn’t exist. But if we pass the test, then the label’s color is changed.
It is important to note here the relative order of evaluation of converters and validators. Validators are called and evaluated before converters so that if a value is not valid we will not attempt to convert or write the value. This is a good thing for the converter, because it can presume that the value has been vetted prior to being passed in to the converter. This can free the converter from having to do checks for bad values such as nulls or division by zero if they are stopped by the validator. This also means that if the converter has side effects (such as caching values) then those side effects will occur only when the values are actually updated.
Setting an initial value
One corner case that can result from a contextual binding is that the attribute being declared on the node will be the source, and usually the declaration of the value is where the bind node goes. There are two ways to solve this. The first is to place the bind node on the target attribute on the target node. But this is not always desirable for many reasons. It may make the code harder to read, or the target node may not be declared and may be a value passed into the script binding. In those cases you can use the
value: attribute to specify a value to be passed into the declaring source node. Why would you need to set the value? Because often the default is not what you want to start off with:
checkbox(“Check spelling before sending mail”,
selected: bind(target:model, ‘spelcheck’, value:true))
Some UI option should just always be turned on by default. The checkbox widget defaults to unselected, and if we don’t set a value then some people will always send poorly written e-mail. After the binding is set up, the value of the source will be set to the value set in the
value: attribute, and if this represents a change, the binding will even automatically fire.
Turning a binding off
Sometimes you don’t need a binding to actually fire the updates automatically. The resulting object representing a fully assembled binding has the options to fire the bindings manually or on demand. This allows the binding to represent the data flow between two different properties without necessarily requiring them to be constantly in sync. The attribute
bind: controls whether or not a biding will be set to automatically update. The attribute accepts Boolean values and by default it is set to true. But when would you ever not want a binding to be automatic? One example is a form, which will directly update the application model preferences when the user presses OK, but they don’t want the changes set immediately. By setting the
bind: option to false the binding updates can be managed manually.