How-to: Creating a custom control
On this page, you will learn how to create a custom control for rendering properties of a specific object type. We’ll be doing this in the following steps:
Creating a custom ViewElementBuilder that can be specified on a property.
Using the custom ViewElementBuilder when a specific viewElementType is specified.
Automatically using a specific viewElementType for properties of a specific object type.
Creating a custom ViewElementBuilder
In this section, we’ll be creating a custom ViewElementBuilder
for a specific object type, that can be re-used in various rendering pipelines.
As an example, we’ll be creating a control for a User
object, so that a read-only value will be rendered as the avatar and the display name.
Starting off, we’ll create a custom ViewElementBuilder
by extending ViewElementBuilderSupport
.
public class UserValueViewElementBuilder extends ViewElementBuilderSupport (1)
{
@Override
protected MutableViewElement createElement ( ViewElementBuilderContext builderContext ){
User user = EntityViewElementUtils.currentPropertyValue( builderContext, User.class ); (2)
if ( user != null ) {
return BootstrapUiBuilders.div()
.css( "user-control" )
.add( BootstrapUiBuilders.node( "img" )
.name( "avatar" )
.css( "user-avatar" )
.attribute( "src", user.getAvatar() )
.add( BootstrapUiBuilders.span()
.css( "user-name" )
.add( BootstrapUiBuilders.text( user.getDisplayName() ) ) )
).build();
}
return BootstrapUiBuilders.text( "" ).build() (3)
}
}
1 | The ViewElementBuilder extends ViewElementBuilderSupport .
ViewElementBuilderSupport provides utility methods for creating a ViewElement as well as registering additional resources and processing. |
2 | The property that is being rendered is fetched from the ViewElementBuilderContext so that we have access to the properties of the object that is rendered. |
3 | If there is no value present, a default ViewElementBuilder is used. |
Using the above setup, we could simply render a User
property with the following configuration.
public void configure( EntitiesConfigurationBuilder entities ) { (1)
entities.withType( Book.class )
.properties(
props -> props.property( "author" )
.viewElementBuilder( ViewElementMode.FORM_READ, new UserValueViewElementBuilder() ) (2)
);
}
1 | Entities can be configured by implementing the EntityConfigurer interface.
This allows us to modify entities, their properties and their views when the EntityConfiguration is being built. |
2 | The created ViewElementBuilder is used when rendering a property. |
Rendering a specified type as a custom ViewElement
Once we have our property available, we can register the ViewElementBuilder
to be used when a specific view element type is used.
We’ll create an EntityViewElementBuilderFactory
, which will be used to render the correct ViewElement
for each ViewElementMode
.
@Component (1)
public class UserViewElementBuilderFactory implements EntityViewElementBuilderFactory
{
public final static String USER_CONTROL = UserViewElementBuilderFactory.class.getName() + ".userControl"; (2)
@Override
public boolean supports( String viewElementType ) {
return StringUtils.equals( USER_CONTROL, viewElementType ); (2)
}
@Override
@SuppressWarnings("unchecked")
public ViewElementBuilder createBuilder( EntityPropertyDescriptor entityPropertyDescriptor, ViewElementMode viewElementMode, String viewElementType ) {
if ( !viewElementMode.isForMultiple() ) { (3)
if ( isReadOnly( entityPropertyDescriptor ) && ViewElementMode.isValue( viewElementMode ) ) { (4)
return new UserValueViewElementBuilder();
}
}
return null;
}
private boolean isReadOnly( EntityPropertyDescriptor entityPropertyDescriptor ) {
return !entityPropertyDescriptor.isWritable() && entityPropertyDescriptor.isReadable() && !entityPropertyDescriptor.isHidden();
}
}
1 | The EntityViewElementBuilderFactory is automatically picked up as a bean. |
2 | When a property is rendered with the specified view element type, this EntityViewElementBuilderFactory will generate a control in the specific value mode.
In this case, the control is prefixed with the fully qualified path of the factory, to ensure a unique view element type is registered. |
3 | We only want to use the control in a case where a single user is rendered. |
4 | We only want to use the specified builder when a readonly value is rendered, e.g. as a value in a list, or as a value in a formGroup. |
This allows us to refactor the configuration in the previous section to the following.
public void configure( EntitiesConfigurationBuilder entities ) {
entities.withType( Book.class )
.properties(
props -> props.property( "author" )
.viewElementType( ViewElementMode.CONTROL, UserViewElementBuilderFactory.USER_CONTROL ) (1)
);
}
1 | Instead of specifying a ViewElementBuilder , the view element type can be specified. |
Finally, we’ll be creating a ViewElementTypeLookupStrategy
, which will automatically use the view element type we’ve just defined when rendering properties of a given object type.
@Order(1) (1)
@Component
public class UserViewElementLookupStrategy implements ViewElementTypeLookupStrategy (2)
{
@Override
public String findElementType ( EntityPropertyDescriptor entityPropertyDescriptor, ViewElementMode viewElementMode ){
Class<?> propertyType = entityPropertyDescriptor.getPropertyType();
if ( propertyType != null && User.class.isAssignableFrom( propertyType ) ) { (3)
if ( ViewElementMode.isValue( viewElementMode ) ) {
return UserViewElementBuilderFactory.USER_CONTROL; (3)
}
}
return null;
}
1 | Our component is registered with Order(1) to ensure that it is used before the default ViewElementTypeLookupStrategy . |
2 | A ViewElementTypeLookupStrategy is created to automatically resolve the view element type for a given property. |
3 | In this case, we only want to specify the view element type for a property of the object type User when it is rendered in a value mode. |
This allows us to entirely omit the EntityConfiguration
that was defined earlier.