How-to: Creating a custom form view
In this how-to we’ll show you how to create an entity view with a form backed by a custom model object.
What we’ll do:
create a form model object representing our form data
creating an EntityViewProcessor that fetches, validates, saves and renders our model
add a custom form view and have it show up as a separate tab item
Some background
Creating an additional form view for an entity is a very common action.
Often you want to manage data that does not actually represent entity type properties.
You can attach this data to an existing (or separate) form by registering an extension on the EntityViewCommand
.
Using extensions has the advantage that data binding and default (annotation based) validation will happen automatically.
If you add an extension to the default entity form (having the SaveEntityViewProcessor ) and the extension has errors upon validation, that will count as global validation and will block saving the entity.
This makes for a very easy way to extend a default form with custom behaviour.
|
Create the extension class
The extension class is the model for our form and will be validated when the form is submitted.
@Data
static class MyExtension
{
@NotBlank
private String url;
@Min(1980)
@Max(2000)
private int creationYear;
}
Create an EntityViewProcessor for the extension
Render the extension on the form and manage the form data. Annotation based validation will be done on form POST.
/**
* Custom EntityViewProcessor for MyExtension form.
* Does not specify an extensionName() but uses the default one (class name),
* as all processing will be done inside this implementation and there will
* never be multiple instances of this processor for a single view.
*
* Builds a simple form manually.
* Validation is actually done transparantly using annotation validation.
* Form elements will show errors because the control names match the extension properties.
*/
static class MyExtensionViewProcessor extends ExtensionViewProcessorAdapter<MyExtension>
{
@Autowired
private CustomEntityValidator customEntityValidator;
@Override
protected MyExtension createExtension( EntityViewRequest entityViewRequest,
EntityViewCommand command,
WebDataBinder dataBinder ) {
return new MyExtension();
}
@Override
protected void doPost( MyExtension extension,
BindingResult bindingResult,
EntityView entityView,
EntityViewRequest entityViewRequest ) {
if ( !bindingResult.hasErrors() ) {
// Put a dummy feedback message
entityViewRequest.getPageContentStructure()
.addToFeedback(
BootstrapUiBuilders.alert()
.success()
.dismissible()
.text( "Updated url with: " + extension.getUrl() )
.build()
);
}
}
@Override
protected void validateExtension( MyExtension extension, Errors errors, HttpMethod httpMethod, EntityViewRequest entityViewRequest ) {
customEntityValidator.validate( extension, errors );
}
@Override
protected void render( MyExtension extension,
EntityViewRequest entityViewRequest,
EntityView entityView,
ContainerViewElementBuilderSupport<?, ?> containerBuilder,
ViewElementBuilderMap builderMap,
ViewElementBuilderContext builderContext ) {
builderMap.get( SingleEntityFormViewProcessor.LEFT_COLUMN, ColumnViewElementBuilder.class )
.add(
formGroup()
.label( "URL" )
.control( textbox()
.controlName( controlPrefix() + ".url" )
.text( extension.url ) )
)
.add(
formGroup()
.label( "Creation year" )
.control( textbox()
.controlName( controlPrefix() + ".creationYear" )
.text( "" + extension.creationYear ) )
);
}
@Override
protected void postRender( MyExtension extension,
EntityViewRequest entityViewRequest,
EntityView entityView,
ContainerViewElement container,
ViewElementBuilderContext builderContext ) {
EntityViewContext entityViewContext = entityViewRequest.getEntityViewContext();
// Manually change the cancel button url
container.find( "btn-cancel", ButtonViewElement.class )
.ifPresent( button -> button.setUrl(
entityViewContext.getLinkBuilder().update( entityViewContext.getEntity() )
) );
}
}
Register the view with our processor
The view itself can be registered under any name on the entity configuration. The view name will be used in the message code resolving.
When registering the view, some of the EntityViewCustomizers
are used to specify an admin menu item (tab) should be rendered for this view.
// Use a configuration template for a simple extension form
// Configure the view to create a menu item under the advanced options
entities.withType( ... )
.formView(
"extension",
EntityViewCustomizers.basicSettings()
.adminMenu( "/advanced-options/extension" )
.andThen( EntityViewCustomizers.formSettings().forExtension( true ) )
.andThen( builder -> builder.viewProcessor( new MyExtensionViewProcessor() ) )
);
Translate the menu item title
Set the right message code for the specific view menu item.
# Default value for every entity with that view
EntityModule.entities.adminMenu.views[extension]=My extension
# Specific title for the menu item on myEntity page
MyModule.entities.myEntity.adminMenu.views[extension]=Extra Fields