Architecture
Cross-Sectional Data Binding
Although the state of the UI can be mostly defined by information from PMOs and the domain model, it sometimes must be additionally influenced by other factors, such as system state, for instance. In linkki we call this concept cross-sectional data binding because it influences the data binding of multiple fields in a cross-sectional way.
A good example for such aspects is the "browse mode". Many applications distinguish between:
-
"Edit Mode", in which data can be entered and modified on the UI
-
"Browse Mode", in which data in forms is shown, but can’t be modified
Often, applications toggle between these modes, for instance when "Editing" is started and completed. In browse mode all form fields should be read-only. This global editing state of the application thus influences many fields at once. Additionally, the browse mode overrides settings from PMOs. Even when a field is marked as editable by the PMO, it is write protected during browse mode.
linkki provides two technical means to implement cross-sectional data binding: PropertyDispatcher and PropertyBehaviors.
PropertyDispatcher
The BindingContext (see BindingContext Basics - BindingManager) uses a PropertyDispatcherFactory, which creates multiple linked PropertyDispatchers. These dispatchers in turn are used to bind PMO properties (see Binding of further UI element properties). PropertyDispatchers have methods to determine the data type and value of a property. By default linkki creates four linked dispatchers, where each delegates to the next if it can’t provide the value:
-
BehaviorDependentDispatcherusesPropertyBehaviors(see next section, PropertyBehaviors) -
BindingAnnotationDispatcherprovides information that can be read directly from UI annotations -
ReflectionPropertyDispatcheruses reflection to access methods on PMO or domain model derived from property names -
ExceptionPropertyDispatcherthrows an exception if the required property is not found. This often indicates a spelling error in the method name, which causes theReflectionPropertyDispatcherto be unable to find the proper method
Custom dispatchers can be created via a PropertyDispatcherFactory. There, the method createCustomDispatchers can be overridden to create one or more custom dispatchers. These dispatchers are inserted between BehaviorDependentDispatcher and BindingAnnotationDispatcher.
The DefaultBindingManager offers a constructor to pass a custom PropertyDispatcherFactory. If set, the DefaultBindingManager uses this custom PropertyDispatcherFacotry for all BindingContexts that are created by using 'getContext()'.
PropertyBehaviors
PropertyBehaviors are created by a PropertyBehaviorProvider that can be passed to a BindingContext as constructor parameter. In this way, the application can configure which PropertyBehaviors should be used in the BindingContext.
A BehaviorDependentDispatcher can control the behavior of properties through PropertyBehaviors. Only a selection of properties can be controlled with it: Visibility (isVisible), editability (isWritable) and visibility of errors (isShowValidationMessages). For this, all PropertyBehaviors are called; if all return true, the BehaviorDependentDispatcher sends the request to the next PropertyDispatcher, otherwise it returns false or an empty error list, or doesn’t set the value.
For custom behaviors the interface PropertyBehavior can be implemented. It also offers static constructor methods to delegate one of the controlled properties to another function:
PropertyBehaviorProvider behaviorProvider = PropertyBehaviorProvider
.with(PropertyBehavior.readOnly(() -> readOnly));
Using PropertyBehaviors with a BindingManager
If a BindingContext should be managed by a BindingManager, it can be created using one of the methods BindingManager#createContext(Class/String, PropertyBehaviorProvider). The created context will then use the given behaviors. Note that these methods will throw IllegalArgumentExceptions if a context with the given name or class already exists.
PropertyBehaviorProvider behaviorProvider = PropertyBehaviorProvider
.with(PropertyBehavior.readOnly(() -> readOnly));
The default implementation DefaultBindingManager also provides a constructor in which a default PropertyBehaviorProvider can be defined that is then used for all BindingContexts created by using getContext(). Calling createContext with a PropertyBehaviorProvider would result in a BindingContext that only uses the provided behaviors, ignoring the default property behaviors of the DefaultBindingManager. If the default behaviors should not be replaced but only extended, DefaultBindingManager.getDefaultBehaviorProvider() allows access to the PropertyBehaviorProvider from which a new one can be created with PropertyBehaviorProvider.append(PropertyBehavior…).