FileDownloader without button

Hi guys, I am using Vaadin 8.

Usually I use FileDownloader related to the button. I mean, I have a button to download a file, and when I click on it, I download the file.

Now I have a button which starts an export in csv format. I would, in the end of this process, download to the user, automatically (without button click), the file just created.

How can I do it?

Thanks

Thanks for reply.

I tried already to do a similar thing. See my try (I write only the button to export and download the file):

public void buttonClick(ClickEvent event) {
    try {
        // products variable is a list of Product entity
        // productService.exportProducts returns file just created (is a temp file)  
        File exportFile = productService.exportProducts(products);
        StreamResource downloadResource = createFileResource(exportFile);
        downloadResource.setFilename("export.csv");
        FileDownloader fileDownloader = new FileDownloader(downloadResource);

    } catch (IOException e) {
        LOG.error("", e);
    }
}

private StreamResource createFileResource(File file) {

        StreamResource sr = new StreamResource(new StreamSource() {

            @Override
            public InputStream getStream() {

                try {
                    return new FileInputStream(file);

                } catch (IOException e) {
                    LOG.error(e.getMessage());
 
                }
                return null;
            }
        }, null);

        sr.setCacheTime(0);

        return sr;
    }

This doesn’t work for me :frowning:

What I wrong or miss?

Hi Fabio,

You can just give the FileDownlaoder a StreamResource of your CSV file. Have a look at how it’s done in this
example
.

Hope this helps,
Goran

Hi Fabio,

It will not work that way, as you are creating the FileDownloader in the button click event handler.
Instead you should extend the button outside of the click handler, as it is given in the example using somehting like:

StreamResource myResource = createResource(); FileDownloader fileDownloader = new FileDownloader(myResource); fileDownloader.extend(button); –Goran

Thanks Goran,
but the issue is that I have a button does:

  1. create csv
  2. download the file automatically (I would)

You are saying me that I have to do

fileDownloader.extend(exportButton);

how usually I do…

So in the end I have to do as follow:

Button export = new Button(I18NHelper.i18n("ui.product.export"), new ClickListener() {
    private static final long serialVersionUID = 1989848854345501210L;

    @Override
    public void buttonClick(ClickEvent event) {
        try {
            // products variable is a list of Product entity
            // productService.exportProducts returns file just created (is a temp file)  
            File exportFile = productService.exportProducts(products);
            StreamResource downloadResource = createFileResource(exportFile);
            downloadResource.setFilename("export.csv");
            FileDownloader fileDownloader = new FileDownloader(downloadResource);
            fileDownloader.extend(event.getButton());

        } catch (IOException e) {
            LOG.error("", e);
        }
    }
});

I just tried this and it doesn’t work :frowning:

Try creating the FileDownloader outside the buttonClick event instead and extend the button right after you’ve created the button. Now you are creating the FileDownloader when the click has already happened.

-Olli

I cannot put the FileDownloader outside, because is inside button click that the file is created…

Maybe you are saying…

in the button click listener create the file and then I call a method which create FileDownloader?

I just tried:

export = new Button(I18NHelper.i18n("ui.product.export"), new ClickListener() {
    private static final long serialVersionUID = 1989848854345501210L;

    @Override
    public void buttonClick(ClickEvent event) {
        try {
            Query<Product, Filter> query = new Query<Product, Filter>(null);

            Stream<Product> products = dp.fetch(query);

            exportFile = productService.exportProducts(products);
            downloadResource = createFileResource(exportFile);
            downloadResource.setFilename("export.csv");
            download();

        } catch (IOException e) {
            LOG.error("", e);
        }
    }
});

.....

private void download() {

        FileDownloader fileDownloader = new FileDownloader(downloadResource);
        fileDownloader.extend(export);
    }

It doesn’t work

FileDownloader needs the downloadResource, which I create in the event button… and then I have some NPE

The exportFile is created when I click the button… I don’t understand your code how fit with my goal, sorry

Hello Fabio,

Would the section
Lazily Determine the Content and the Name of the file being served
from[i]

[/i]
Letting The User Download A File
help you?

/** This specializes {@link FileDownloader} in a way,
  * such that both the file name and content can be determined
  * on-demand, i.e. when the user has clicked the component. */

Hope it helps!

Thanks, I already read this, but it doesn’t help me :frowning:

Hi,

How about you try something like this:

[code]
Button export = new Button(I18NHelper.i18n(“ui.product.export”));
File exportFile = productService.exportProducts(products);
StreamResource downloadResource = createFileResource(exportFile);
downloadResource.setFilename(“export.csv”);

// now create the file downloader with the stream resource and extend the button
FileDownloader fileDownloader = new FileDownloader(downloadResource);
fileDownloader.extend(export);
[/code]I haven’t run this code, let me know how it goes.

–Goran

Would this work for you?
Basically you delay the StreamResource creation until the download has triggerd by the client.
Of course products has to be effectively final for this to work.

HTH
Marco

Supplier<StreamResource> fileSupplier = () -> createFileResource(productService.exportProducts(products));
FileDownloader fileDownloader = new FileDownloader(() -> null) {
   @Override
   public Resource getFileDownloadResource() {
      setFileDownloadResource(fileSupplier.get());
      return super.getFileDownloadResource();
   }
};
fileDownloader.extend(export);

You can’t create the download after the fact and trigger it programmatically, browsers these days prevent that, there is pretty much no way around it.

You
can
go the suggested lazy route, so that the download is triggered automatically right away when the click happens, and effectively just be so slow about it that you have time to generate the file on the fly. It seems trickier at the first glance, but don’t be discouraged by that – the lazy route works.

Yes, that should work. You specify the stream and filedownloader when you create the view, and you create the file within you getStream() method within your StreamResource.

Many thanks!

I solved this issue creating a foo resource method for the creation of FileDownloader, then, for listener event, I set the “true” resource method. Make sense for you?

final FileDownloader fileDownloader = new FileDownloader(
        new StreamResource(new StreamResource.StreamSource() {
            public InputStream getStream() {
                return null;
            }
        }, "")
);

final Button myButton = new Button("Download file", listener -> {
    fileDownloader
            .setFileDownloadResource(createAwesomeCSVResource());
});

fileDownloader.extend(myButton);

How to use a button to generate a file and download it in the same action event

The solution from above did not work for me (the listener was triggered after download started).
To solve the problem I needed to create DynamicFileResource with a reflection hack (because Vaadin team decided to make the method FileResource.setSourceFile(File sourceFile) private).


	@SuppressWarnings("serial")
	public static class DynamicFileResource extends FileResource {

		protected Callable<String> processor;

		/** provide the source file using callback */
		public DynamicFileResource(Callable<String> processor) {
			super(new File("."));
			this.processor = processor;
		}

		@Override
		public DownloadStream getStream() {
			try {
				// process and set the resulted file before returning stream
				String filePath = processor.call();
				setSourceFile(new File(filePath));
			} catch (Exception e) {
				throw new IllegalStateException("Unable to generate file resource", e);
			}
			return super.getStream();
		}

		// set sourceFile (maybe Vaadin team should make the method setSourceFile(File sourceFile) public..)
		public void setSourceFile(File sourceFile) {
			// access private member: FileResource.sourceFile	
			try {
				Field f = FileResource.class.getDeclaredField("sourceFile");
				f.setAccessible(true); // force accessible
				f.set(this, sourceFile);
			} catch (Exception e) {
				throw new IllegalStateException("Unable to set sourceFile", e);
			}
		}

	}
	
	
	public enum EXPORT_TYPE {
		CSV, EXCEL, PDF
	}

	/** generated and download report */
	private void attachReportExporter(Button button, final EXPORT_TYPE exportType) {
		FileDownloader fileDownloader = new FileDownloader(new DynamicFileResource(() -> generateReport(exportType)));		
		fileDownloader.extend(button);
	}
	
	private String generateReport(EXPORT_TYPE exportType) {
        // generate the report and return the path to resulted file
        ...
        return filePath;
	}
	
	
	// sample usage attachExportReport()
	private void createLayout() {
		btnExportCsv = new Button("Export Csv");		
		attachReportExporter(btnExportCsv, EXPORT_TYPE.CSV);
		
		btnExportExcel = new Button("Export Excel");
		attachReportExporter(btnExportExcel, EXPORT_TYPE.EXCEL);
		
		btnExportPdf = new Button("Export PDF");
		attachReportExporter(btnExportPdf, EXPORT_TYPE.PDF);
	}