Downloads with JavaFX WebView


Question

my web application offers a download. Javascript creats at the click the url (it depends on the user input) and the browser should open it, so that the page isn't reloaded.

For that, I think I have to alternatives:

// Alt1:
window.open(pathToFile);

// Alt2:
var downloadFrame = document.getElementById('downloads');
if (downloadFrame === null) {
  downloadFrame = document.createElement('iframe');
  downloadFrame.id = 'downloads';
  downloadFrame.style.display = 'none';
  document.body.appendChild(downloadFrame);
}
downloadFrame.src = pathToFile;

Both works under Firefox. Problem with open new window method: If the creation of the file at the server needs more time, the new empty tab will be closed late. Problem with iframe: If there is an error at the server, no feedback is given.

I think at firefox the iframe is the better solution. But the web application must run with an JavaFX WebView, too. JavaFX haven't a download feature, I have to write it. One possible way is to listen on the location property:

final WebView webView = new WebView();

webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
    @Override public void changed(ObservableValue<? extends String> observableValue, String oldLoc, String newLoc) {
        if (newLoc.cotains("/download")) {
            FileChooser chooser = new FileChooser();
            chooser.setTitle("Save " + newLoc);
            File saveFile = chooser.showSaveDialog(webView.getEngine().getScene().getWindow());

            if (saveFile != null) {
                BufferedInputStream  is = null;
                BufferedOutputStream os = null;
                try {
                    is = new BufferedInputStream(new URL(newLoc).openStream());
                    os = new BufferedOutputStream(new FileOutputStream(saveFile));

                    while ((readBytes = is.read()) != -1) {
                      os.write(b);
                    }
                } finally {
                    try { if (is != null) is.close(); } catch (IOException e) {}
                    try { if (os != null) os.close(); } catch (IOException e) {}
                }
            }
        }
    }
}

There are some problems:

  1. The download start depends on a part of the url, because JafaFX supports no access to the http headers (that is bearable)
  2. If the user starts the download with the same url two times, only the first download works (the change event only fires, if the url is new). I can crate unique urls (with #1, #2 and so on at the end). But this is ugly.
  3. Only the "window.open(pathToFile);" method works. Loading an iframe don't fire the change location event of the website. That is expectable but I haven't found the right Listener.

Can someone help me to solve 2. or 3.?

Thank you!

PS: Sorry for my bad english.

edit: For 2. I found a way. I don't know if it is a good one, if it is performant, if the new webview is deleted or is in the cache after download, .... And the user don't get an feedback, when some a problem is raised:

webView.getEngine().setCreatePopupHandler(new Callback<PopupFeatures, WebEngine>() {
    @Override public WebEngine call(PopupFeatures config) {
        final WebView downloader = new WebView();
        downloader.getEngine().locationProperty().addListener(/* The Listener from above */);
        return downloader.getEngine();
    }
}
1
2
10/1/2014 8:48:30 AM

I think you may just need to use copyURLtoFile to get the file...call that when the location changes or just call that using a registered java class. Something like this:

org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));

Using copyURLToFile the current page doesn't have to serve the file. I think registering the class is probably the easiest way to go... something like this:

PHP Code:

<a href='#' onclick="app.getfile('$filename' )">Download $filename</a>

Java (in-line class in your javafx class/window... in this case my javafx window is inside of a jframe):

public class JavaApp {
JFrame cloudFrameREF;
    JavaApp(JFrame cloudFrameREF)
    {
       this.cloudFrameREF = cloudFrameREF; 
    }

    public void getfile(String filename) {
    String newLoc = "http://your_web_site.com/send_file.php?filename=" + filename;
    org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
     }
 }

This part would go in the main javafx class:

Platform.runLater(new Runnable() {
        @Override
        public void run() {
browser2 = new WebView();
webEngine = browser2.getEngine();
appREF = new JavaApp(cloudFrame);
 webEngine.getLoadWorker().stateProperty().addListener(
        new ChangeListener<State>() {
          @Override public void changed(ObservableValue ov, State oldState, State newState) {

              if (newState == Worker.State.SUCCEEDED) {
              JSObject win
                            = (JSObject) webEngine.executeScript("window");
// this next line registers the JavaApp class with the page... you can then call it from javascript using "app.method_name".
                    win.setMember("app", appREF);  
            }

            }
        });

You may not need the frame reference... I was hacking some of my own code to test this out and the ref was useful for other things...

1
7/6/2018 5:25:47 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon