Controller: Binding Things Together
Neither view nor STComponent shown above contain any code to communicate with each other, and of course domain model is completely independent from both. Communication task is performed by the bindings layer. It does all the wiring without endless custom decorators and adapters. Internally it consists of a set of generic adapters for Swing components that allow to "plug" certain model properties or controller actions to the known "sockets". Another term for such "plug/socket" pair is "binding". In the most simple case a binding establishes a data channel between "bound" property of the view and some property of the model, but without a need for special model or listener interfaces. At the same time View and Model stay unaware of each other as all communications are indirect. This preserves the fundamental MVC concerns separation.
Each component is wired with a "binding set" - a number of predefined named bindings, some of which are mandatory, some - optional. The actual implementation of a binding set is provided by JStaple and depends on the specific view (as internally a binding has to use whatever API Swing provides, including anonymous inner classes and specialized models), however the API used inside controller to work with the bindings is the same regardless of the underlying communication mechanism. Also we can do better than just connecting model property to a view property. As demonstrated below, there is a few types of bindings, e.g. binding STComponent actions to certain view events, etc.Bindings are made really dynamic by adding a scripting language to the mix. Current JStaple implementation uses OGNL for scripting.
Bindings layer is declarative and separate from STComponent. JStaple has a fully working bindings prototype that is configured inside STComponent. However it should be easy to extract all bindings in a separate XML. This way bindings can be changed and reloaded dynamically without recompiling, but more importantly they will not pollute the code.
Here is an example of binding to a JComboBox residing in a custom component. It demonstrates a number of things that can be done with bindings. Note that there is no need to subclass JComboBox, as bindings work with all standard Swing widgets (as well as custom components).
BindingBuilder builder = new BindingBuilder(controllerObject);builder.switchToSubview("departmentsCombo");builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING,
"departments");builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING,
"departmentLabel(#item)");builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING,
"selectedDepartment");builder.bindAction(STComboBoxBindings.ACTION_BINDING,
"showEmployeesAction()");builder.bindToPullValuesFromModel("visible", "connected");
Now going through the individual parts...
BindingBuilder builder = new BindingBuilder(controllerObject);builder.switchToSubview("departmentsCombo");
Above we create a BindingBuilder which is simply a helper class to hide binding internals. The second line tells the builder that we are about to start configuration of a "departmentCombo" property of the View. It is expected to be a JComboBox.
builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING,
"departments");
Here we set one of the "standard" JComboBox bindings. The names of standard bindings for different Swing components are defined in the corresponding interfaces in JStaple. For example this and other JComboBox bindings are defined in STComboBoxBindings interface. Second parameter to a method is the model property name, which happens to be "departments". Properties are resolved using STComponent as a "root" object. As the binding is created using "bindToPullValuesFromModel" method, it works one-way, automatically updating the view whenever the model changes.
builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING,
"departmentLabel(#item)");
The line above shows how easy it is to build a display label for a given object in the list in a way defined by the controller for this specific view. A string displayed in a JComboBox doesn't have to be a result of "department.toString()", or even a property of the Department class (though it can be in some cases). We can do better than that. With a little piece of OGNL script used as a second argument we can delegate a decision about display string to the controller object:
The actual implementation of "departmentLabel" method by the controller may for instance prefix a given department name with a dash:
public String departmentLabel(Department department) {return " - " + department.getName();}
Next line...
builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING,
"selectedDepartment");
This line shows how to synchronize the selected object between JComboBox and the model. The object that is passed around is a Department (as this is the type of objects in the bound list), not the String label displayed in the combo box. There is no need for developer to manually convert between Department and string label - JStaple takes care of such conversion.
The previous binding sets a selected Department property of the controller, but we can also bind an action method that should be invoked whenever selected department changes. Here is how this can be done:
builder.bindAction(STComboBoxBindings.ACTION_BINDING,
"showEmployeesAction()");
STComboBoxBindings interface declares just a few bindings specific to JComboBox. At the same time a typical JComponent may have a dozen or more properties; some may need to be controlled by the application. This brings us to another cool feature - binding to an arbitrary property. The example below shows how to setup a binding that changes JComboBox visibility based on the "connected" property of the model:
builder.bindToPullValuesFromModel("visible", "connected");
JComboBox example above doesn't feel like Swing anymore. It is clean and does not contain anonymous inner classes or specialized models. So lets reiterate over what can be accomplished using bindings:
Bindings are a part of the controller used for indirect communication (e.g. passing data or invoking an action) between (a) view and model and (b) view and STComponent.
Bindings provide an infrastructure for the clean hierarchical MVC design using Swing, AWT and custom components.
Each view element (i.e. each Swing or custom widget) may have a predefined set of bindings. User components can declare bindings too. For instance a dialog can have two bindings: "okAction" and "cancelAction". Depending on which dialog button was clicked, one of them will be activated when the dialog is closed, resulting in an invocation of a bound action method of the caller object. All this is possible without defining special listener interfaces or knowing anything about the caller.
Bindings for a given component are independent from each other, so there is no need for a specialized model for each component type. Instead different pieces of the domain model can be bound via different bindings, resulting in the ultimate flexibility.
If a binding is not recognized as a "standard one", the name of the binding is treated as a view property.
From the view standpoint, bindings can be "pull", "push" or "two-way".
STComponent actions can be bound just as easy as property values.
Just like the event-based approach, bindings decouple view from model, though bindings go much further in this respect.
Unlike event-based MVC bindings do not require special model interfaces or anonymous inner classes.
There are other things bindings can help with that are not yet fully explored in JStaple. For instance bindings can provide generic hooks for input validation. It should be possible to install a validator object that is called every time a binding pushes a value from the view to the model. Validation failures can be handled in a consistent fashion, e.g. by changing the color of the view element and setting a tooltip to display a validation message.
|