How-to: Add a custom form with a file upload
This how-to will cover the basics of creating an additional form view.
A short summary about the topics discussed:
Create a file upload with validation
Create your own form view on a entity
Register a new property on a form view that is rendered by EntityModule
Create our file dto class
First we have to create a class that can act as a dto object for our uploaded file. We can also use this to set our validation annotations if desired.
@Setter
@Getter
public class FileRequest {
@NotNull
private MultipartFile[] files;
}
Register a new form view on an entity
To create a form view, all we have to do is register modify the configuration of our entity in an EntityConfigurer
.
@Configuration
class DummyEntityConfiguration implements EntityConfigurer {
@Override
public void configure(EntitiesConfigurationBuilder entities) {
entities.withType(DummyEntity.class)
.formView("upload", fvb -> fvb
.properties(props -> props.property("files") (1)
.displayName("Csv file") (2)
.viewElementBuilder( (3)
ViewElementMode.CONTROL, BootstrapUiBuilders.file()
.controlName("extensions[upload].files")
)
.writable(true) (4)
)
.showProperties("files")
.removeViewProcessor(SaveEntityViewProcessor.class.getName()) (5)
);
}
}
1 | A new property files is registered which we will use to represent the file upload. |
2 | By configuring a display name we modify the label that will be rendered. |
3 | A ViewElementBuilder is provided to override how the control should be rendered.
Since we’re creating a file upload form we’ll override the builder to ensure that our rendered ViewElement is a file input.
To bind our form control to the FileRequest#files property, we’ll also provide a control name.
In this case we’ll be mapping to an extension which will be configured in the view processor later on. |
4 | The end user should be able to select a file, so we set the property to writable. |
5 | When a form is submitted, we expect our data to be saved, which is done by the SaveEntityViewProcessor .
In this case, we merely want to receive the uploaded files and then process them instead of saving the attached entity, so we’ll remove this specific processor. |
Take a look at Configuring entity types for more in depth knowledge on configuring entities. |
Access the view
Now that our custom form view has been registered we can access it by navigating to the following path on our application: admin/entities/dummyEntity?view=upload
.
This path is the base path to the DummyEntity
type, for which we have just configured an additional form view with the name upload
.
By adding the additional view
parameter we can specify which view should be rendered.
You can find out more about how entity views work here. |
Handling our form submit.
Thus far we’ve created a custom form view which renders the required controls.
The next step is to handle the uploaded file, so we’ll create a view processor that extends the ExtensionViewProcessorAdapter
.
An ExtensionViewProcessorAdapter
is a specific EntityViewProcessorAdapter
which makes it easier to work with extensions.
Extensions are a map of additional properties on the EntityViewCommand and can be used to render/receive additional data.
Extensions are automatically bound to the EntityViewCommand when submitted, during which base validation is applied as well.
To learn more about extensions, take a look at EntityViewCommand.
|
We’ll take care of handling the form data in four steps:
-
Ensuring that the data is bound to the aforementioned DTO (
FileRequest
) when the form is submitted. -
Registering the view processor on the configuration.
-
Ensuring that our form is configured for multi-part data.
-
Processing the received data.
First off we’ll create the processor.
@Component
public class FileUploadViewProcessor extends ExtensionViewProcessorAdapter<FileRequest> {
@Override (1)
protected String extensionName() {
return "upload";
}
@Override (2)
protected FileRequest createExtension(EntityViewRequest entityViewRequest, EntityViewCommand command, WebDataBinder dataBinder) {
return new FileRequest();
}
}
1 | We provide the name of the extension that is registered in this processor.
This will make sure that extension[upload] will be mapped to the entity defined in this processor. |
2 | In this case, we want to map the submitted form data to a FileRequest object, so we’ll register a new instance to which the form data can be bound. |
Now that we have our view processor we can add it to our configuration class.
@Configuration
@RequiredArgsConstructor
class DummyEntityConfiguration implements EntityConfigurer {
private final FileUploadViewProcessor fileUploadViewProcessor; (1)
@Override
public void configure(EntitiesConfigurationBuilder entities) {
entities.withType(DummyEntity.class)
.formView("upload", fvb -> fvb.viewProcessor(fileUploadViewProcessor) (1)
.properties(props -> props.property("files")
// ...
)
// ...
);
}
}
1 | The view processor is wired and added to the upload form view. |
Next up, we’ll ensure that our form is configured as a multipart form, by setting the encryption type on the form that is currently rendered.
@Override
protected void postRender(EntityViewRequest entityViewRequest, EntityView entityView, ContainerViewElement container, ViewElementBuilderContext builderContext) {
container.find("entityForm", FormViewElement.class)
.ifPresent(form -> form.setEncType(FormViewElement.ENCTYPE_MULTIPART));
}
Finally we’ll handle our form submission.
@Override
protected void doPost(FileRequest extension, BindingResult bindingResult, EntityView entityView, EntityViewRequest entityViewRequest) {
if (!bindingResult.hasErrors()) { (1)
MultipartFile[] files = file.getFiles(); (2)
// ... Handle the submitted files.
}
}
1 | Only handle data if no validation errors have occurred. |
2 | Retrieve the upload files so they can be handled. |
It’s also possible to quickly add a feedback message and/or set a redirect url after handling the data. Example setting a feedback message
Example setting a redirect url
You can also use the |