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<@NonNull BindValue> {

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

        private String getPmoProperty(BindValue bindAnnotation, AnnotatedElement annotatedElement) {
            String pmoProperty = bindAnnotation.pmoProperty();
            if (StringUtils.isEmpty(pmoProperty)) {
                if (annotatedElement instanceof Method) {
                    pmoProperty = BeanUtils.getPropertyName((Method)annotatedElement);
                } else if (annotatedElement instanceof Field) {
                    pmoProperty = ((Field)annotatedElement).getName();
                } 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");
                }
            }
            return pmoProperty;
        }

    }

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;
    }