Extending linkki

Faktor-IPS Extension

linkki is often used in conjunction with Faktor-IPS. The module linkki-ips-vaadin23 provides some useful functionalities for this combination.

To use the extension simply include the following Maven dependency:

<dependency>
    <groupId>org.linkki-framework</groupId>
    <artifactId>linkki-ips-vaadin23</artifactId>
</dependency>

Fields for Faktor-IPS Data Types

A new UI component annotation @UIDecimalField supports the Faktor-IPS data type Decimal. It is quite similar to @UIDoubleField but supports Decimal instead of Double.

Other Faktor-IPS-specific fields - for example a Money field - may follow.

Faktor-IPS Model Validation

Validating a Faktor-IPS object yields messages for the validation framework on the business layer. linkki uses its own validation message implementation in the UI layer. To convert these messages from Faktor-IPS to linkki, the Faktor-IPS extension provides a MessageConverter. Objects are converted according to the following table:

Faktor-IPS linkki Notes

org.faktorips.runtime.Message

org.linkki.core.binding.validation.message.Message

org.faktorips.runtime.MessageList

org.linkki.core.binding.validation.message.MessageList

org.faktorips.runtime.ObjectProperty

org.linkki.core.binding.validation.message.ObjectProperty

The MessageConverter currently does not map the index field

org.faktorips.runtime.IMarker

org.linkki.ips.messages.ValidationMarkerWrapper

linkki only uses the isRequiredInformationMissing() method

Calling the Faktor-IPS validation method and converting the messages should be done directly in a ValidationService, which is then provided to the BindingManager.

A minimal validation service using lambda notation
ValidationService validationService = () -> MessageConverter
        .convert(ipsModelObject.validate(new ValidationContext(UiFramework.getLocale())));

When the function above is called by linkki, the Faktor-IPS object is validated according to the defined validation rules. Rules can add messages to the returned MessageList, potentially referencing specific fields using ObjectProperty.

These messages are then converted, and displayed to the user. Messages referring to an ObjectProperty bound by linkki to a UI field, are shown in the UI accordingly. It is possible to define further styling.

Faktor-IPS Property Dispatcher

A special PropetyDispatcher called IpsPropertyDispatcher could be used in a binding context to automatically retrieve information from the Faktor-IPS model when using appropriate model binding.

Supported apsects

The following aspects are supported:

Label

In case you do not specify a label for a UI component in your PMO, the dispatcher tries to retrieve the label from the underlying model object. That means, if your underlying model object is generated by Faktor-IPS and the associated bound model attribute is a Faktor-IPS attribute, it returns the label of this Faktor-IPS attribute.

Required

If the required attirubte in the UI annotation is set to its default value NOT_REQUIRED, the IpsPropertyDispatcher checks the value set of the corresponding attribute. The field is then set to REQUIRED if the value set does not contain null.

Visible

If the visible attribute in the UI annotation is set to its default value VISIBLE, the IpsPropertyDispatcher checks the value set of the corresponding attribute. The field is then set to INVISIBLE if the value set is null or empty.

Enabled

If the enabled attribute in the UI annotation is set to its default value ENABLED, the IpsPropertyDispatcher checks the value set of the corresponding attribute. The field is then set to DISABLED if the value set is null or empty.

IpsPropertyDispatcher only tries to derive the the required, visible and enabled state from the model if the attribute in the UI annotation is set the its default value. If DYNAMIC is used, the dynamic aspect method will be effective instead of the IpsPropertyDispatcher. This mechanism can be used to override the behavior of IpsPropertyDispatcher.
How to include IpsPropertyDispatcher

To include the IpsPropertyDispatcher as a custom dispatcher in the BindingContext, the IpsPropertyDispatcherFactory can be provided to the constructor of the DefaultBindingManager. If you already have a custom PropertyDispatcherFactory you could simply instantiate the IpsPropertyDispatcher in your subclass using the factory method IpsPropertyDispatcher#createIpsPropertyDispatcher.

Using IpsPropertyDispatcherFactory
BindingManager bindingManager = new DefaultBindingManager(validationService,
        PropertyBehaviorProvider.NO_BEHAVIOR_PROVIDER, new IpsPropertyDispatcherFactory());

PROPERTY Constants as Model Attributes

Faktor-IPS generates constants for defined attributes containing their name. These values can be used as the modelAttribute to easily create bound UI components.

If the used property is removed from the Faktor-IPS model while still being referenced from an annotation, a compilation error is generated. This would not be the case if hardcoded strings were used.

Excerpt of a class generated by Faktor-IPS
public static final String PROPERTY_STRING = "string";


/**
 * The name of the property unrestrictedInclNull.
 *
 * @generated
 */
public static final String PROPERTY_UNRESTRICTEDINCLNULL = "unrestrictedInclNull";

/**
 * The name of the property unrestrictedExclNull.
 *
 * @generated
 */
public static final String PROPERTY_UNRESTRICTEDEXCLNULL = "unrestrictedExclNull";

/**
 * Max allowed values for property unrestrictedExclNull.
 *
 * @generated
 */
public static final ValueSet<String> MAX_ALLOWED_VALUES_FOR_UNRESTRICTED_EXCL_NULL = new UnrestrictedValueSet<>(
        false);

/**
 * The name of the property emptyValueSet.
 *
 * @generated
 */
public static final String PROPERTY_EMPTYVALUESET = "emptyValueSet";

/**
 * Max allowed values for property emptyValueSet.
 *
 * @generated
 */
public static final OrderedValueSet<Marker> MAX_ALLOWED_VALUES_FOR_EMPTY_VALUE_SET = new OrderedValueSet<>(false,
        null);

@IpsAttribute(name = "string", kind = AttributeKind.CHANGEABLE, valueSetKind = ValueSetKind.AllValues)
@IpsGenerated
public String getString() {
    return string;
}
Using the class above as a model object
@ModelObject
private final IpsModelObject modelObject;

@UITextField(position = 0, modelAttribute = IpsModelObject.PROPERTY_STRING)
public void getString() {
    // model binding
}