How to achieve "click-button-to-download-file" in vaadin 7

Hello Vaadin users,

I’m currently in the process of migrating several vaadin 6 apps to vaadin 7. The apps use the following construct extensively for reports generation:

Since Vaadin 7 removed the Window.open() functionality, now we’re supposed to rely on several “extensions” instead, namely BrowserWindowOpener and FileDownloader. The problem with these extensions is that there’s no way to simply achieve “click this button and the user will get a download file dialog” functionality.
Vaadin wiki has an entry about “downloading a file”
but this is very limited because it’s a static resource IMO.

In my use cases, i can’t call FileDownloader.extend() or BrowserWIndowOpener.extend() in my init() because these classes require a Resource, and at that particular point in time, the Resource has not been created yet. Even if i can somehow create the Resource, imagine a page with 20/30 buttons, each one running a query or storedproc to generate reports (pdf/xls/html/csv/what-have-yous). Pre-loading all of them is simply not an option.

So, to repeat the subject line, how do we go about replicating this in Vaadin7?
Thanks very much in advance,
ts.

EDIT:
Links to similar threads (no solutions so far):

https://vaadin.com/forum/-/message_boards/view_message/2581493


https://vaadin.com/forum/-/message_boards/view_message/2446007

EDIT 2:

Book of Vaadin has a rather relevant page
that says:

Imitating the approach, I can now create a two-button setup (e.g. first: “Prepare Report” and second: “Download PDF”, for example); Users will have to click the first button (which will update the resource/visibility/clickability etc for the second button, then click the second button to actually download the thing). If i actually implement it this way, I will never hear the end of it from my users (>__< )

How about if you give the FileDownloader a StreamResource and generate the actual contents on-demand.

I managed to achieve something similar. What I did was:


public class ExportAction
{
   protected FileDownloader fileDownloader;
   protected Button downloadButton;

   public void initAction(TableView tableView)
   {
        downloadButton = new Button("Download");
        fileDownloader = new FileDownloader(FileDownloadUtils.createFileResource(new File("NoPath")))
        {
            @Override
            public boolean handleConnectorRequest(VaadinRequest request, VaadinResponse response, String path) throws IOException
            {
                createExportContent();
                return super.handleConnectorRequest(request, response, path);
            }
        };
        fileDownloader.extend(downloadButton);
   }

   public void createExportContent()
   {
       try
       {
           final File file = File.createTempFile("export", getFileExtension());
           file.deleteOnExit();

           BufferedOutputStream bufferedStream = new BufferedOutputStream(new FileOutputStream(file));
           generateContent(bufferedStream);
           bufferedStream.close();

           fileDownloader.setFileDownloadResource(FileDownloadUtils.createFileResource(file));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Error exporting!", e);
        }
    }
}

So, during initialization I just give it a non-existent file and when the user clicks the button, the overriden handleConnectorRequest() method first calls another method which creates a file and sets the fileDownloader resource to the newly created file. Call to super then handles the actual download.

This works fine if you have a button to which you can extend the downloader. However my problem is even worse. I have a table and at the top of it I have one button “Download” which opens a ContextMenu with options for several actions (to PDF, to XLS, to CSV). Each of these is an action, with its own run() method (that is each action does not have it’s own button). I can’t figure out how to download the file once the action is run, since I can’t extend anything. Does anyone have an idea? This is a major problem for me…

What type are the GUI elemtns for the actions “to PDF, to XLS, to CSV”? The FileDownloader can extend any AbstractComponent, so you can also use it on other Vaadin GUI Elements.

You can do it like this:


https://vaadin.com/forum#!/thread/2929028

However the file name is predetermined by the application.

I know this is an issue for MenuItem (cannot extend them for downloads, browser window opening). I suspect it won’t work for Tree-based context menus based on Actions.

Seems like there needs to be an entirely standard Vaadin mechanism to do this. Dynamic data downloads are extremely common. Fortunately, while the old StreamResource way is deprecated, it at least continues to work as well as under Vaadin 6.

Is this really deprecated? I didn’t notice any warnings on any of the methods.

pdfDownload = new Button("Download PDF");
StreamResource sr = getPDFStream();
FileDownloader fileDownloader = new FileDownloader(sr);
fileDownloader.extend(pdfDownload);
private StreamResource getPDFStream() {
        StreamResource.StreamSource source = new StreamResource.StreamSource() {

			public InputStream getStream() {
    			ByteArrayOutputStream stream = getPFD();
    			InputStream input = new ByteArrayInputStream(stream.toByteArray());
          		return input;

            }
        };
  	StreamResource resource = new StreamResource ( source, getFileName());
        return resource;
}

getStream() is called only when the button is pressed and in this case the getPDF() method then generates the pdf file for download.

You are right, I wrote that incorrectly. Using a StreamResource isn’t deprecated, but the way of using it previously to trigger a download is. If you are using FileDownloader.extend(), that is the new Vaadin 7-approved way.

If you attach your own click listener and create the StreamResource and then call something like:

Page.getCurrent().open(sr, “_blank”,false);

That is deprecated, but it still works. In Vaadin 6, this was getWindow().open() instead of the Page version. I guess it still suffers from potential popup blocker issues.

If you have a button or other AbstractComponent to click on, FileDownloader (and BrowserWindowOpener) work fine. But this does not work for MenuItem or Table Action handlers because while you can click on them to trigger work, they are not subclasses of AbstractComponent, so we’re still using the deprecated mechanism for those needs.

I am very dissappointed that there is no way to download generated(on fly) file. Most of applications have reports and there is no way to do it. Now I am using deprecated

Page.getCurrent().open(sr, “_blank”,false);

but this is not solution becouse of popup blocker

As Johannes H said, using a StreamResource should work fine. The following code is straight from our test cases:

[code]
resource = new StreamResource(new StreamResource.StreamSource() {
@Override
public InputStream getStream() {
try {
BufferedImage img = createImage();
ByteArrayOutputStream imagebuffer = new ByteArrayOutputStream();
ImageIO.write(img, “png”, imagebuffer);
return new ByteArrayInputStream(imagebuffer.toByteArray());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}, “demo.png”);
new FileDownloader(resource).extend(downloadButton);

[/code]StreamSource.getStream() is only called when the browser requests the file, so the download can be generated dynamically at that point.

Johannes, while I may not understand bekzak’s issue, I think he’s using a stream resource since even his code snippet shows an ‘sr’ parameter to Page.open() which is no doubt a stream resource.

The issue we’ve found is that FileDownloader.extend() does not work with menu items, or with say right-click Action handlers for Tree and Table, yet all of those are clickable, but none are subclasses of AbstractComponent and thus cannot be “extended”.

We have a similar issue with BrowserWindowOpener…the “extend” mechanism doesn’t work for menu items or tree/table actions.

Hi Johannes,

Exceptions in getStream are not catching from errorHandler. When an exception throws without try-catch block, VaadinServlet is handling exception and not showing any notification until a new request coming. How can I solve this?

Thanks,
Beslan.

I’m with you, sometimes need to download generated files (on fly).

You can try Page.getCurrent().open(sr, “_self”,false); to prevent popup.

But this solution is deprecated on vaadin 7.

Same here but a way of downloading resources without a component click event while also avoiding the popup blocker might not be possible.
The Browser get’s suspicious when websites want to open a new window or a link without the client sending a request.
If you use page.open in a button click listener for example it goes:
CLIENT–click event–>SERVER–open resource -->CLIENT.
That’s why the filedownloader extension adds the resource as an attachment right on the client-side when the button is clicked so:
CLIENT – click event → SERVER
|
| attach resource
v
CLIENT

This way the browser doesn’t get suspicious of the file.

I personally would also like a replacement or a de-deprecation of the page.open method, as most of my users already know that they have to turn off the popup blocker to open files, …if nothing else is possible or at least as an addition.

Hi,

I’m trying to implement Mihas method (3rd post), however I get the following Error (in his line 13) when overriding the handleConnectorRequest method:


VaadinResponse cannot be resolved to a type

Can somebody help here?

EDIT: import com.vaadin.server.VaadinResponse; does the job -.-


EDIT 2: But which package do I have to import to get the method FileDownloadUtils.createFileResource(…) to work?

Johannes Dahlström. You are right! Problem was solved by overriding getStream(), Reports are now generated dynamically
I also made generation of file dynamically by overriding getFilename() method of StreamResource class

Question: is there a way to know that the download completed (successfully)? For example our app might need to close a subwindow, or display a notification.

I didn’t see any FileDownloader listeners, or any methods I could overload by subclassing FileDownloader. I could subclass InputStream and overload the close() method, but I suspect that is called before the user is prompted to save the file.

Any suggestions? Thanks.

@Matthew
You can extend the FileDownloader to override the handleConnectorRequest method:

[code]

FileDownloader fileDownloader = new FileDownloader(fileInputStream) {

        private static final long serialVersionUID = -4584979099145066535L;

        @Override

        public boolean handleConnectorRequest(VaadinRequest request, VaadinResponse response, String path) throws IOException {

            boolean result = super.handleConnectorRequest(request, response, path);

            if (result) {

                MyClass.this.doWhatYouWant();

            }

            return result;

        }

    } ;

[/code]However, if (as I do) you remove an object from a table in the “doWhatYouWant()” method, the table does not refresh, presumably because that would be a refresh event inside of a download event. If anyone knows how to get round that, I’d be grateful.

David,
Thanks for the reply. I’m trying what you suggest but it seems that super.handleConnectorRequest is returning too soon: upon the initial download request, but before the download has actually occurred. (In addition, result is true even if my stream resource failed to find the requested file.)

You asked about getting your table to refresh in response. Have you tried calling container.refresh() on the container feeding the table? That lets the table know the next time it goes to repaint that the container data is stale. (My assumption being that the table is already repainting, it’s just using the cached data.)

(If the browser is not sending a server request when you need it to see the change, then you might need to look into using server push to tell the browser to refresh. (https://vaadin.com/book/-/page/advanced.push.html) But I’ve no experience with that myself.)

I created component SimpleFileDownloader https://vaadin.com/directory#!addon/simplefiledownloader
Hope it will help.