Extending linkki

Custom Annotation with Binder

Binder is used for manual binding of UI components that are annotated with the annotation @Bind. The annotation specifies certain properties that are commonly bound to UI components such as the value, as well as the visible/enabled/required states. In addition, it also defines how a bound property is identified with the help of the attributes pmoProperty, modelObject and modelAttribute.

If the annotation does not suit your needs then a custom annotation could be implemented easily. For example, it can be useful if you want to bind each property with an individual annotation.

The following example demonstrates how to design a custom @BindValue annotation to bind only the value of a component:

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.METHOD })
@LinkkiBoundProperty(BindValueAnnotationBoundPropertyCreator.class)
@LinkkiAspect(BindFieldValueAspectDefinitionCreator.class)
public @interface BindValue {

    String pmoProperty() default "";

    String modelObject() default ModelObject.DEFAULT_NAME;

    String modelAttribute() default "";

    ...
}

The custom annotation class must be annotated with another annotation called @LinkkiBoundProperty which provides a BoundProperty. A BoundProperty is necessary as it describes which property in the PMO and the business model should be bound to the annotated component. @LinkkiBoundProperty provides the BoundProperty by defining a creator Class that implements the interface BoundPropertyCreator. This creator must have a default constructor that can be later called by the linkki framework to create a BoundProperty.

In the example, the @BindValue annotation defines the BindValueAnnotationBoundPropertyCreator as its @LinkkiBoundProperty:

    class BindValueAnnotationBoundPropertyCreator implements BoundPropertyCreator<BindValue> {

        @Override
        public BoundProperty createBoundProperty(BindValue annotation, AnnotatedElement annotatedElement) {
            return getPmoProperty(annotation, annotatedElement)
                    .withModelObject(annotation.modelObject())
                    .withModelAttribute(annotation.modelAttribute());
        }

        private BoundProperty getPmoProperty(BindValue annotation, AnnotatedElement annotatedElement) {
            String pmoPropertyName = annotation.pmoProperty();
            if (StringUtils.isEmpty(pmoPropertyName)) {
                if (annotatedElement instanceof Method) {
                    return BoundProperty.of((Method)annotatedElement);
                } else if (annotatedElement instanceof Field) {
                    return BoundProperty.of((Field)annotatedElement);
                } else {
                    throw new IllegalArgumentException("The @" + BindValue.class.getSimpleName()
                            + " annotation only supports reading the property name from " + Field.class.getSimpleName()
                            + "s and " + Method.class.getSimpleName() + "s");
                }
            } else {
                return BoundProperty.of(pmoPropertyName);
            }
        }

    }

The example above works quite similar to the existing @Bind annotation. But it is also possible to define the bound property in a different way. For example, the model attribute and model object might be specified by another annotation.

Finally, the @BindValue annotation needs to define BindFieldValueAspectDefinition as a @LinkkiAspect (see Aspects).

Now the @BindValue annotation can be used with a Binder for manual binding:

    @BindValue(pmoProperty = "zip", modelAttribute = Address.PROPERTY_ZIP)
    private final TextField zipTxt;

Just as the @Bind annotation, the new annotation can also be used on getter methods:

    @BindValue(pmoProperty = "zip")
    public TextField getZipTxt() {
        return zipTxt;
    }