Overview
As The Xul Framework has progressed, the need for a clean MVC architecture underlying applications has become clear. MVC, or Model-View-Controller, provides for a clean separation between application data (models), view logic (controllers) and the user interface (views). This separation creates loosely coupled components that are easier to maintain and more importantly testable. For a more in-depth look at building Pentaho Xul application with the MVC pattern, please refer to the excellent article by Aaron Philips: MVC in Pentaho Xul Applications
At the heart of any MVC application framework lies a method for synchronizing data between the models and views. Many frameworks leave this "housekeeping" to the developer. With the introduction of Xul Bindings, we have provided a more developer friendly approach.
What's in a Binding?
A Xul Binding object consists of four primary and two optional pieces of data:
- Two XulEventSource objects, one "Source" the other "Target" and a property for each to "Bind" together.
- Optionally, A binding strategy (one-way, bi-directional or bind-once).
- Optionally, A Convertor Object to manage data translation between the two.
Creating a Binding Object
There are several methods provided for in the Xul Framework for creating a Binding Object. Some are as simple as calling bind() with four Strings from an Event Handler. We'll not cover all of those here as they're addressed in detail in MVC in Pentaho Xul Applications. Instead, let's have a look from the ground up.
Below is the most basic representation of a binding.
Binding bind = new Binding(sourceObject, "firstName", targetObject, "value");
When added to a Binding Context (covered later) it will synchronize data between "sourceObject" and "targetObject" by way of "firstName" and "value" respectively.
As Java lacks first class support for Properties and writing Objects with direct member access is frowned upon, Bindings access data by way of the Javabean standard. A discussion of Javabeans is outside the scope of this article and we will proceed with the assumption that you are familiar with them.
At the Javabean level our binding looks something like this:
sourceObject.getFirstName() => targetObject.setValue() sourceObject.setFirstName() <= targetObject.getValue()
Binding Strategies
By default, a binding object represents a synchronization bi-directionally. Any change to the source will update the target and visa-versa. You can optionally prescribe for a one-way binding between source and target as seen below:
binding.setBindingType(ONE_WAY);
Where ONE_WAY is a type defined in Binding.Type enumeration. A one-way binding will send data from the source to the target but not the other way around.
Future versions will introduce the concept of a "bind-once" strategy that will in essence "flash" a snapshot of data between objects at the time of the binding's instantiation.
Conversions
We can further extend the flexibility of our bindings by providing a BindingConvertor object to manage the translation of data between objects. The BindingConvertor class itself is so small worth taking a look at right here.
public abstract class BindingConvertor<V, R> { ... public abstract R sourceToTarget(V value); public abstract V targetToSource(R value); }
Below is a simple implementation where we bind the value of a XulTextbox with the selectedIndex of a XulMenuList. When the user enters a numeric string into the text box it will be converted into an Integer before being passed to the setSelectedIndex method of the menu list.
Binding binding = new Binding(textbox, "value", dropdown, "selectedIndex"); BindingConvertor conversion = new BindingConvertor<String, Integer>(){ @Override public Integer sourceToTarget(String value) { return Integer.parseInt(value); } @Override public String targetToSource(Integer value) { return value.toString(); } } binding.setConversion(conversion);
The Binding Context
Bindings in and of themselves don't actually do anything. They simply describe the Binding relationship between objects. The actual establishment of a binding is performed by a BindingContext object. However, you never have to deal with a BindingContext directly as every XulDomContainer has one. To add a new Binding object to the context, simply pass the binding to the addBinding() method of the XulDomContainer.