Loading...
Important Notice - Forums is archived

To simplify things and help our users to be more productive, we have archived the current forum and focus our efforts on helping developers on Stack Overflow. You can post new questions on Stack Overflow or join our Discord channel.

Product icon
TUTORIAL

Vaadin lets you build secure, UX-first PWAs entirely in Java.
Free ebook & tutorial.

Dynamic HTML/image/PDF downloads that open in a browser window/tab

David Wall
9 years ago Feb 07, 2014 5:27pm

I've been using FileDownloader and our OnDemandFileDownloader scheme under Vaadin 7.1.10 (and before) with good success except for the lack of support in making it work for non-components (like a MenuItem being clicked, Actions, item clicked in a Select box, etc.).

But we have dynamically generated HTML, images and PDFs (meaning some are generated while others are retrieved from a database -- they are not files stored in a file system) that we'd like to be able to click and then show so that they appear in a popup browser window or new browser tab and avoid the file download save/open dialog.  Is this possible and what's the best approach for doing it?

One problem with the Save/Open dialog is that if my default browser for HTML, say is Firefox, but I'm using Chrome or IE, when I click the open option, it often opens the downloaded file in Firefox rather than the browser I was using.  That's confusing to be sure.

Friendof Vaadin
9 years ago Feb 08, 2014 3:27pm
David Wall
9 years ago Feb 13, 2014 7:26pm

Friendof Vaadin: If you send the binary data with the correct mime type, the user's browser will open (or embed) the file, e.g. a PDF file, in the browser window.

Instead of FileDownloader and OnDemandFileDownloader, you could use a StreamResource, as in the example in paragraph 4.4.5. Stream Resources in the Vaadin book

https://vaadin.com/book/-/page/application.resources.html

This chapter also contains links to the chapter on request handlers, for a more general approach to serve dynamic content.

Yes, we already do something like that, but generally we end up with code like this:

Page.getCurrent().open(sr, "_blank", true);

to make that stream resource open as a popup window. 

That works fine, but that method is deprecated and so the assumption is there's a "new correct standard" way to allow someone to click on a button to download dynamically generated content that must be done "on the fly" as we cannot actually produce all of the dynamic content for all of the buttons "just in case."
 

Ted Carroll
9 years ago Feb 18, 2014 1:14pm
David Wall
9 years ago Mar 19, 2014 12:55am

I created a class OnDemandBrowserWindowOpener that subclasses BrowserWindowOpener and otherwise mirrors changes made for our OnDemandFileDownloader (subclasses FileDownloader).

It seems to work, but I wanted to see if any Vaadin experts can tell if the code is useful and correct for our purpose, which is to open up a browser window using dynamic content.  So we may send back a PDF or HTML file that is retrieved from our database (and thus has no URL or the like).

And like the other, the key reason is we don't want to actually fetch the dynamic content unless the button is clicked to trigger it being opened up.

Here's our code.  Feedback is welcome:

public class OnDemandBrowserWindowOpener extends BrowserWindowOpener {
    private static final long serialVersionUID = 2448069691276636815L;

    /**
     * Provide both the {@link StreamSource} and the filename in an on-demand way.
     */
    public interface OnDemandStreamSource extends StreamSource {
        public String getFilename ();
    }
    
    public static class OnDemandStreamResource extends StreamResource {
        private static final long serialVersionUID = -1087853524501280897L;

        protected HashMap<String,String> parameterMap;
        
        public OnDemandStreamResource(OnDemandStreamSource streamSource) {
            super(streamSource,"dummyFileName");
        }
        
        public DownloadStream getStream() {
            DownloadStream ds = super.getStream();
            
            if ( parameterMap != null ) {
                for( String key : parameterMap.keySet() ) {
                    ds.setParameter(key, parameterMap.get(key));
                }
            }
            
            // If the cache time is still the default, let's reduce for our dynamic content that shouldn't be cached for a day.
            if ( ds.getCacheTime() == DownloadStream.DEFAULT_CACHETIME ) {
                ds.setCacheTime(10L * 60L * 1000L); // 10 minutes in msecs
            }
            
            return ds;
        }
        
        public void setParameterMap(HashMap<String,String> parameterMap) {
            this.parameterMap = parameterMap;
        }
    }

    protected final OnDemandStreamSource onDemandStreamSource;
    protected HashMap<String,String> parameterMap;
    protected Button buttonToEnableWhenDone;
    protected String contentType;
    protected Integer cacheTime;

    public OnDemandBrowserWindowOpener(OnDemandStreamSource onDemandStreamSource) {
        super(new OnDemandStreamResource(onDemandStreamSource));
        this.onDemandStreamSource = onDemandStreamSource;
    }

    public OnDemandBrowserWindowOpener(Button buttonToEnableWhenDone, OnDemandStreamSource onDemandStreamSource) {
        this(onDemandStreamSource);
        this.buttonToEnableWhenDone = buttonToEnableWhenDone;
    }

    @Override
    public boolean handleConnectorRequest(VaadinRequest request, VaadinResponse response, String path) throws IOException {
        try {
            OnDemandStreamResource sr = getResource();
            sr.setFilename( onDemandStreamSource.getFilename() );
            if ( contentType != null ) {
                sr.setMIMEType(contentType);
            }
            if ( cacheTime != null ) {
                sr.setCacheTime(cacheTime);
            }
            if ( parameterMap != null ) {
                sr.setParameterMap(parameterMap);
            }
            return super.handleConnectorRequest(request, response, path);
        } finally {
            if ( buttonToEnableWhenDone != null ) {
                buttonToEnableWhenDone.setEnabled(true);
            }
        }
    }

    public OnDemandStreamResource getResource() {
        return (OnDemandStreamResource)this.getResource(BrowserWindowOpenerState.locationResource);
    }
    
    public void setContentType(String v) {
        contentType = v;
    }
    
    public void setCacheTime(int msecs) {
        cacheTime = msecs;
    }
    
    public void setStreamSourceParameter(String name, String value) {
        if ( parameterMap == null )
            parameterMap = new HashMap<String,String>();
        parameterMap.put(name, value);
    }
}

We then use it something like this:

final Button downloadDocumentSubButton = new Button(label);
            downloadDocumentSubButton.setStyleName(Reindeer.BUTTON_LINK);
            downloadDocumentSubButton.setDisableOnClick(true);

            OnDemandBrowserWindowOpener opener = new OnDemandBrowserWindowOpener(downloadDocumentSubButton, new OnDemandBrowserWindowOpener.OnDemandStreamSource() {
                @Override
                public InputStream getStream() {
                        String html = tpd.tpd.getSnapshotDocument(); // this is just how we retrieve the HTML object from the DB
                        byte downloadHtmlData = EsfString.stringToBytes(html);
                        return new BufferedInputStream(new ByteArrayInputStream(downloadHtmlData));
                }

                @Override
                public String getFilename() { // currently not working as expected (not shown in the resulting URL)
                    String htmlFileName;
                    if ( tpd.tpd.hasDocumentFileName() )
                        htmlFileName = tpd.tpd.getDocumentFileName();
                    else {
                        EsfVaadinUI vaadinUi = EsfVaadinUI.getInstance();
                        htmlFileName = vaadinUi.getMsg("TranSnapshotPopupButton.document.filename",tpd.documentName,tpd.partyName);
                    }

                    return htmlFileName;
                }
            
            });
            opener.setContentType(Application.CONTENT_TYPE_HTML+Application.CONTENT_TYPE_CHARSET_UTF_8);
            opener.setCacheTime(3600000); // 3600 secs (1 hour) in msecs
            opener.extend(downloadDocumentSubButton);
            layout.addComponent(downloadDocumentSubButton);    

The resulting URL in my browser window is:
http://localhost/open-eSignFormsVaadin7/ui/APP/connector/0/245/url/dummyFileName

I'll need to investigate a bit more, as I wanted it to be the filename I return in my getFileName() method, which is called, but the value clearly doesn't go through and the default "dummyFileName" is still used.
 

David Wall
9 years ago Mar 19, 2014 9:21pm