Handling file uploads and managing stored files is a common requirement in web applications. However, creating a robust and user-friendly interface can be challenging. This post demonstrates how to build a file management application using Vaadin Flow, showcasing how Vaadin's components, data binding, and backend integration simplify the process of uploading, storing, and displaying files.
Core components: Upload and FileGrid
The user interacts with the application primarily through the UploadView
. This view incorporates an Upload component, allowing users to select and upload files. The uploaded data is temporarily stored in a MemoryBuffer
for further processing. The SucceededListener
reads this data, creates a new FileEntity
object, and triggers the editFile
method in the FileUploadForm
to handle the uploaded file.
Upload upload = new Upload();
MemoryBuffer buffer = new MemoryBuffer();
upload.setReceiver(buffer);
upload.addSucceededListener(event -> {
upload.clearFileList();
FileEntity fileEntity = new FileEntity();
fileEntity.setFileName(event.getFileName());
try {
fileEntity.setFileData(buffer.getInputStream().readAllBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
uploadForm.editFile(fileEntity);
});
The FileGrid
component, extending the Vaadin Grid component, displays a tabular view of uploaded files. This versatile component is popular for its ability to:
- Efficiently handle large datasets with minimal code.
- Offer built-in support for selection, drag-and-drop, and other common table interactions.
- Allow customization of columns, including defining custom renderers and editors.
public FileGrid(FileService service, FileUploadForm fileUploadForm) {
this.service = service;
this.uploadForm = fileUploadForm;
addColumn(FileEntity::getFileName).setHeader("File Name").setAutoWidth(true);
addColumn(FileEntity::getDescription).setHeader("Description").setAutoWidth(true);
addComponentColumn(file -> {
Button editButton = new Button("Edit");
editButton.addClickListener(e -> uploadForm.editFile(file));
Button deleteButton = new Button("Delete");
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
deleteButton.addClickListener(e -> {
service.deleteFile(file);
refreshGrid();
Notification.show("File deleted!");
});
HorizontalLayout actionsLayout = new HorizontalLayout(new FileDownload(file), editButton, deleteButton);
actionsLayout.setAlignItems(FlexComponent.Alignment.BASELINE);
actionsLayout.setSpacing(true);
return actionsLayout;
}).setHeader("Actions");
The addComponentColumn
method seen above empowers us to add custom actions for each file within the grid, specifically downloading, editing, and deleting. The edit button triggers the editFile
method in the FileUploadForm
to allow modification of file details.
The delete button both removes the file from the system and refreshes the grid. Finally, the download button utilizes the StreamResource
class from the Vaadin Server package. This functionality is encapsulated in a separate FileDownload
class for improved readability, reusability, and testability. It is pictured below.
private static class FileDownload extends Anchor {
public FileDownload(FileEntity fileEntity) {
super();
add(VaadinIcon.DOWNLOAD.create());
StreamResource resource = new StreamResource(fileEntity.getFileName(),
() -> new ByteArrayInputStream(fileEntity.getFileData()));
resource.setCacheTime(0);
resource.setContentType("application/octet-stream");
setHref(resource);
getElement().setAttribute("download", true);
setTitle("Download the file");
}
}
Data binding with Binder and BeanValidationBinder
The Vaadin Binder class simplifies the process of binding data between UI fields and data properties. In this project, the FileUploadForm
utilizes the Binder
class and its subclass, BeanValidationBinder
, to manage data binding between the description TextArea
and the FileEntity
object.
The BeanValidationBinder
leverages JSR-303 annotations to automatically validate user input against the defined constraints found in the FileEntity
class. For example, if we add the @NotNull
annotation to the description field, the BeanValidationBinder
ensures the user provides a description before submitting the form.
Here's a code snippet demonstrating the data binding setup:
Binder<FileEntity> binder = new BeanValidationBinder<>(FileEntity.class);
private final FileService service;
public FileUploadForm(FileService service) {
this.service = service;
add(new VerticalLayout(
description,
new HorizontalLayout(save, cancel)
));
binder.bindInstanceFields(this);
This simplifies our data handling and ensures data integrity within the application.
Conclusion
This demonstrates how Vaadin Flow facilitates the development of user-friendly web applications. Developers can create robust solutions with minimal effort by utilizing core components like Upload and Grid, leveraging data binding with Binder, and integrating with backend services.
Ready to start building your own applications with Vaadin Flow? Explore the latest Vaadin tutorial. You can also find the code for this example on GitHub.