Download link or button

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:

val streamRegistration: StreamRegistration = VaadinSession.getCurrent().resourceRegistry.registerResource(StreamResource(
                    "document.docx",
                    InputStreamFactory {
                        ByteArrayInputStream(docxTable.toByteArray())
                    }
            ))
log.info(streamRegistration.resourceUri.toString())

I probably could make a custom <a> component that sets the href attribute. Is there a build-in Link component?

Hi Frederik,

Yes, there is the Anchor component you can use to serve the file:

Anchor anchor = new Anchor(streamRegistration.getResource(), "Click to download");
yourLayout.add(anchor);

It creates a <a> on the client-side with a direct link to your file.

How to set start downloaded with button?

A solution is to style the anchor as a button.

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.

One way is wrapping the button in an Anchor. Then it looks like a button, but clicking it starts a download. I’ve made a simple add-on for the purpose: https://vaadin.com/directory/component/file-download-wrapper

You can use it like this:

        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);

But I cant disable that anchor.

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:

UI.getCurrent().getPage().executeJavaScript("window.open($0, $1)", registration.getResourceUri().toString(), "_blank"); 

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());
}

MZ

The following way of navigating to a resource URI:

UI.getCurrent().getPage().setLocation(registration.getResourceUri());

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:

UI.getCurrent().getPage().executeJavaScript("window.open($0, $1)", registration.getResourceUri().toString(), "_blank");

or

UI.getCurrent().getPage().open(registration.getResourceUri().toString());