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:
-
BehaviorDependentDispatcher
usesPropertyBehaviors
(see next section, PropertyBehaviors) -
BindingAnnotationDispatcher
provides information that can be read directly from UI annotations -
ReflectionPropertyDispatcher
uses reflection to access methods on PMO or domain model derived from property names -
ExceptionPropertyDispatcher
throws an exception if the required property is not found. This often indicates a spelling error in the method name, which causes theReflectionPropertyDispatcher
to be unable to find the proper method
Custom dispatchers can be created via a PropertyDispatcherFactory
(in its own BindingContext
). There the method createCustomDispatchers
must be overwritten to create one or more custom dispatchers. These dispatchers are inserted between BehaviorDependentDispatcher
and BindingAnnotationDispatcher
.
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(editMode::isReadOnly));
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(editMode::isReadOnly));
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…)
.