I have a button with a click listener that generates a binary document. How do I offer the document to the user? I am already registering the stream like so:
But currently there’s no way to initiate a file download from a click event of a button. Not without some workarounds on the client-side. The cleaner way is to just disguise the anchor as a button and make the user interact with it.
Button button = new Button("Click to download");
FileDownloadWrapper buttonWrapper = new FileDownloadWrapper(
new StreamResource("foo.txt", () -> new ByteArrayInputStream("foo".getBytes())));
buttonWrapper.wrapComponent(button);
layout.add(buttonWrapper);
The advantage of Anchor is that it’s simple, it works and it properly manages the lifecycle of the stream resource (i.e. it unregisters it properly, to avoid memory leaks). Therefore, I prefer to use the Anchor whenever I can, perhaps styled as a button (or wrapping a button) if need be. The anchor still uses StreamSource which can generate/render any downloadable contents necessary.
However, the issue with Anchor is that it for example cannot perform any checks before the download. In such cases you can use the following code snippet to use a button to download stuff:
Button button = new Button("Click me", event -> {
boolean isCheckPassed = true;
if (!isCheckPassed) {
Notification.show("Unfortunately you can not download this file");
} else {
final StreamResource resource = new StreamResource("foo.txt",
() -> new ByteArrayInputStream("foo".getBytes()));
final StreamRegistration registration = VaadinSession.getCurrent().getResourceRegistry().registerResource(resource);
UI.getCurrent().getPage().setLocation(registration.getResourceUri());
}
});
The above only works with Vaadin 14+; with Vaadin 13 there is no Page.setLocation() so use the following workaround:
I don’t know if it’s a recommended way, but I use an Anchor styled with display: none (not setVisible(false), otherwise it is disabled server-side too) and, whenever I need to download something, I use the following snippet of code:
protected void forceDownload(StreamResource resource) {
this.downloadWidget.setHref(resource); // downloadWidget is an Anchor
//
UI.getCurrent().getPage().executeJavaScript("$0.click();", this.downloadWidget.getElement());
}
works, but it causes the current UI instance to be terminated, makes the UI completely un-operational and the following message in the console: Trying to invoke method on not yet started or stopped application, so the page has to be reloaded in this case, which is not a good UX in most cases.
Doing this through the blank option works and keeps UI operational: