Data Binding in Commerce Manager
Commerce Manager uses the Eclipse DBF (Data Binding Framework) to bind the UI controls to the underlying model objects. The binding framework (along with our enhancements) can handle data validation, conversion to model datatypes, and display/clearing of messages related to data entry errors. The main interface is com.elasticpath.cmclient.core.binding.EpControlBindingProvider
.
When you have a new control that you want to bind to a model object, you need a DataBindingContext
within which to bind the control. Typically, one editor, one dialog box, or one wizard page will have one DataBindingContext
, and once the Context has no validation errors then the page is considered to be in a state that can be persisted.
Elastic Path Commerce enhances the data binding framework so that when a control is bound to the model, eclipse FieldAssist decorators are used to display validation errors in the UI next to the field which is failing validation.
The following sections will review the basics of databinding for different controls, and then how to use databinding in Wizard Pages, Dialog Boxes, and Editors, since the architecture to setup binding in each is slightly different.
Where to bind controls in different top-level composits
Binding in Wizard Pages
When creating a WizardPage
, create controls, populate the controls, and then bind the controls. After binding the controls, call the creation factory method for the WizardPageSupport
class. This class uses the decorator pattern to add data binding support to your wizard page. The data binding support automatically ensures that the Finish and Next buttons in the wizard are not enabled unless the validation errors in the page’s DataBindingContext
are resolved.
By default, WizardPages
show any validation messages just under the title (one at a time, the most recent is always the one displayed). Since we’re using field decorations instead, you must override the WizardPage method setErrorMessage(String errorMessage)
to do nothing rather than setting the error text in the page header area.
Binding in Dialog Boxes
Data binding in an implementation of AbstractEpDialog
is similar to data binding within a WizardPage
, but you need to make call to the EpDialogSupport
class’ create()
method after you bind your controls.
Binding in Editors
Typically an editor will contain Managed Forms containing SectionPart
s which are implementations of AbstractCmClientEditorPageSectionPart
. This class and its supers require that you override the abstract bindControls()
method, within which you will use the EpControlBindingProvider
as usual to bind your controls.
How Databinding Works
The Data binding framework provides support for binding individual Value
’s and for binding List
’s of objects. Since we currently don’t have any code that uses the List support, this document will focus on the DBF support for Value binding.
EpControlBindingProvider
API
There are three API methods that will be used most often, and the one you need to use depends on how your control should be bound to your model.
All of the following returns a EpValueBinding
object containing the newly created Binding, and the ControlDecoration
used in the binding. This allows bindings to be added and removed at runtime.
bind(DataBindingContext, control, target, fieldname)
The first method is the most basic, and should be used when you are not concerned with validating the input from your control. Perhaps your control is a Button
which can only have binary states, so there’s no point in validating or converting input.
bind(DataBindingContext, control, validator, converter, updateStrategy, hideDecorationOnFirstValidation)
This method is used when your control’s value cannot be bound directly to the model object’s field, but must perform extra processing to ensure that the model value is properly set. This method is also used when the model field to be set depends on the selected dialog box locale. For example, a dialog box that is used to enter a Product Name and allows the user to select a locale that is currently being edited, the control and model field for productName
are not mapped. You might be editing the French product name or the English product name. In such cases, write a custom ObservableUpdateValueStrategy
implementation, overriding the doSet()
method to call the appropriate setter method on the model object.
removeEpValueBinding(DatabindingContext context, EpValueBinding binding)
Although you are not required to explicitly remove binding on a control when your composite is closed, you might need to remove a binding depending on user input and when it’s appropriate for a model should cease to be updated. This method will remove the EpValueBinding
from the DataBindingContext
, stop validation on the field and its decorations will be hidden. The aggregate validation status will also be affected automatically.
Detailed databinding architecture
Ultimately, to bind a Control to a model object’s field the DataBindingContext
requires four things:
TargetObservableValue
- AnIObservableValue
implementation, typically the UI widget that you want to bind toYou must provide this to the
EpControlBindingProvider
.ModelObservableValue
- AnIObservableValue
implementation of a field in the bean representing the model object that you want to bind toIf you pass in the model object and the name of the field to which you want to bind, the
EpControlBindingProvider
will actually construct this for you using the DBFBeansObservables
factory. If you cannot map your control’s value directly to a model object’s field, don’t provide the target and field name. In that case, theEpControlBindingProvider
will fake one for you.TargetToModelUpdateStrategy
- a class that tells the DBF how to validate and convert the given Control’s value so that it’s suitable for setting on the model objectThe
EpControlBindingProvider
usually creates a default one and sets the given validator and converter, but you can provide your own of required.ModelToTargetUpdateStrategy
- a class that tells the DBF how to validate and convert the given model object’s field so that it’s suitable for setting in the ControlWith the Commerce Manager, we do not use the DBF to update the UI based on changes in the Model (we handle this manually), so the
EpControlBindingProvider
will use a statically definedPOLICY_NEVER
to indicate that this should never happen
The UpdateValueStrategy
required by the DBF has been enhanced through creation of an ObservableUpdateValueStrategy
. The enhancement allows an UpdateStrategy
to be observed so that when validation is performed on a Control’s input then listeners can be informed of the new Validation status of the control.
Determining aggregate validation status
Determining the aggregate validation status of an entire Databinding
Context is usually performed by the EP framework code, but on the rare occasion when custom code must perform such a task you have a couple of options:
Call
getBindings()
on the DBC (Data Binding Context) to get a list of all the bindings and loop through them all, checking the validation status on each Binding. Not recommended.Use the
AggregateValidationStatus
class that is part of the DBF. An example follows.The following code was pulled from
EpDialogSupport
. You can see that it not only uses theAggregateValidationStatus
to determine the validation status of a DBC, but it also adds a change listener to the aggregate status to perform an action when the status changes://com.elasticpath.cmclient.core/src/main/java/com/elasticpath/cmclient/core/binding/EpDialogSupport.java aggregateStatus = new AggregateValidationStatus(dbc.getBindings(), AggregateValidationStatus.MAX_SEVERITY); aggregateStatus.addValueChangeListener(new IValueChangeListener() { public void handleValueChange(final ValueChangeEvent event) { currentStatus = (IStatus) event.diff.getNewValue(); handleStatusChanged();
Binding different types of Controls (examples)
The types of controls currently supported by the databinding framework are:
org.eclipse.swt.widgets.Text
org.eclipse.swt.widgets.Combo
andorg.eclipse.swt.custom.CCombo
org.eclipse.swt.widgets.Button
org.eclipse.swt.widgets.List
Our implementation of the ComboBox
binding assumes that ComboBoxes
only have one object at a time selected, but the underlying framework does not have such a restriction.
Lists are bound in a different manner than the other controls due to the fact that they can have multiple objects selected in them. Typically, listboxes are used only to select existing instances of an object, so the use of a validation and binding framework is not strictly necessary and can usually be avoided. As of this writing we do not bind any listboxes in the Commerce Manager.
Text boxes
//create
Text usernameTextbox = new Text();
//populate
usernameTextbox.setText("myUsername");
//bind
EpValueBinding userNameBinding =
EpControlBindingProvider.getInstance().bind(
myDataBindingContext,
usernameTextbox,
myUser,
"username",
EpValidatorFactory.STRING_255,
null,
hideDecorationOnFirstValidation);
...
//Remove Binding
EpControlBindingProvider.removeEpValueBinding(myDataBindingContext, userNameBinding );
Combo boxes
Binding a combo box is a bit tricky because the combo box usually has an index that doesn’t correspond to the value that must be assigned to the model object’s value. In this case you need to use a custom UpdateStrategy
that tells the databinding framework how to translate the selected combobox index into the value that the model object is expecting. Validation is not usually required in these cases because combo boxes are not usually editable, and the converter is also not required because all the work can be done in the UpdateStrategy
. The following code snippet shows how you might do this:
//com.elasticpath.cmclient.fulfillment/src/main/java/com/elasticpath/cmclient/fulfillment/editors/customer/CustomerDetailsProfileBasicSection.java
bindingProvider.bind(bindingContext, this.statusCombo, null, null, new ObservableUpdateValueStrategy() {
@Override
protected IStatus doSet(final IObservableValue observableValue, final Object value) {
int status;
final int selectionIndex = (Integer) value;
switch (selectionIndex) {
case 0:
status = Customer.STATUS_ACTIVE;
break;
case 1:
status = Customer.STATUS_DISABLED;
break;
default:
return new Status(IStatus.WARNING, FulfillmentPlugin.PLUGIN_ID, "Can not set the customer status."); //$NON-NLS-1$
}
customer.setStatus(status);
return Status.OK_STATUS;
}
}, true);
Buttons
Radio Buttons and CheckBox buttons often need to be bound. Nothing special has to be done for these controls because they don’t need to be validated or converted; they’re typically either true or false.
EpControlBindingProvider.getInstance().bind(
getBindingContext(),
this.cmUserButton,
UserDetailsPage.this.getModel(),
"cmAccess"); //$NON-NLS-1$
List boxes
We do not currently bind listboxes.
Two-way databinding
Adding property change support
The model object being bound must implement the property change listener methods as per the JavaBean specification. Additionally, any property you wish to create a model to ui binding for needs to trigger a property change event when its setter is called.
To get the basic property change methods you can extend the class AbstractListenableEntityImpl
(for objects representing domain entities with guids) or AbstractListenableValueObjectImpl
(for other domain objects). To trigger the property change events you need to modify your setters like in the example below.
//core/ep-core/src/main/java/com/elasticpath/domain/order/impl/AbstractOrderShipmentImpl.java
protected void setItemSubtotal(final BigDecimal itemSubtotal) {
BigDecimal oldItemSubtotal = this.itemSubtotal;
this.itemSubtotal = itemSubtotal;
firePropertyChange("itemSubtotal", oldItemSubtotal, itemSubtotal); //$NON-NLS-1$
}
Binding configuration
If you require two-way databinding (ui control to model as well as model to ui control) then you need to use the EpBindingConfiguration
object to specify your binding settings. This is then passed into the EpControlBindingProvider which handles the actual binding. This method can also be used to create a one-way ui to model binding or a one-way model to ui binding. There are a couple different constructors for the EpBindingConfiguration
object depending on whether you need to use a custom update strategy or not. The most common way of creating a new EpBindingConfiguration
is as follows:
EpBindingConfiguration bindingConfig =
new EpBindingConfiguration(
bindingContext,
control,
model,
"fieldName"); //$NON-NLS-1$
After this you are required to call at least one of the configure*()
methods before passing it into the EpControlBindingProvider
.
Configure UI to Model binding
To add ui to model binding to your binding configuration you need to call one of the configureUiToModelBinding(...)
methods on it. The most commonly used one, which takes a converter and validator, is as follows:
bindingConfig.configureUiToModelBinding(
new EpStringToBigDecimalConverter(),
EpValidatorFactory.BIG_DECIMAL,
false);
Configure Model to UI binding
To add model to ui binding to your binding configuration you need to call the configureModelToUiBinding(...)
method on it as follows:
bindingConfig.configureModelToUiBinding(
new EpBigDecimalToStringConverter(),
UpdatePolicy.UPDATE);
Perform the binding
Once your binding configuration is complete, you can set the binding as follows:
EpControlBindingProvider.getInstance().bind(bindingConfig);
Examples
final EpControlBindingProvider bindingProvider = EpControlBindingProvider.getInstance();
/**
* Example 1: one-way model to ui binding
*
* Use this when all of the following are true:
* - you have an uneditable control displaying a property of the model object
* - the value of that property in the model could be changed by some other event
* - you require the control to update itself when that property is changed
*/
EpBindingConfiguration bindingConfig =
new EpBindingConfiguration(
bindingContext,
itemSubTotalText,
shipment,
"itemSubtotal"); //$NON-NLS-1$
bindingConfig.configureModelToUiBinding(
new EpBigDecimalToStringConverter(),
UpdatePolicy.UPDATE);
bindingProvider.bind(bindingConfig);
/**
* Example 2: two-way binding
*
* Use this when all of the following are true:
* - you have an editable control displaying a property of the model object
* - you require the model to be updated when the control is modified
* - the value of that property in the model could be changed by some other event
* - you require the control to update itself when that property is changed
*/
bindingConfig = new EpBindingConfiguration(
bindingContext,
shippingCostText,
shipment,
"shippingCost"); //$NON-NLS-1$
bindingConfig.configureUiToModelBinding(
new EpStringToBigDecimalConverter(),
EpValidatorFactory.BIG_DECIMAL,
false);
bindingConfig.configureModelToUiBinding(
new EpBigDecimalToStringConverter(),
UpdatePolicy.UPDATE);
bindingProvider.bind(bindingConfig);