Files Demo

First of all, a big thank you to the JavaFX community for warmly welcoming us on Twitter. In just a couple of days, you were 100+ to follow us on our brand-new @WebFXProject account! And JFX-Central kindly listed us in their Links of the Week on September 30, including our previous blog post and demo. Thank you all for your interest in WebFX! ❤️

Thank you also for your questions. And it appeared that the most frequent question asked so far was about accessing local files. So we investigated this possibility, and we are glad to announce that now WebFX can access local files. We made this brand-new Files demo to demonstrate this.

This demo lets you select local files with either the Choose files button or drag & drop. The files are listed with their icon and other simple details, and a thumbnail is displayed for images. A grid view is also available with just the thumbnails. You can open your images in full size, and navigate using left & right keys, left & right mouse clicks, or left & right swipes on touch screens. Same with your audio files, which are played together with the equaliser from our DemoFX library. Video files will be played very soon (only the audio is supported for now). Text files are displayed in a TextArea.

The issue with the JavaFX FileChooser API

Our initial investigation was rather bad news: it appeared that it was impossible to emulate the JavaFX FileChooser API in the browser, mainly for the 2 following reasons:

  • In JavaFX, opening the file chooser is done programmatically through a call to FileChooser.showOpenDialog(), which is a blocking call - something prohibited in JavaScript. In the browser however, it is done through a user interaction with the HTML input element <input type="file"> which is rendered by default with this (ugly) button:

  • The Web File object returned by the browser sandbox has very limited information about the actual local file. For example, it doesn’t let you know its location on the local device, and its URL is a kind of random UUID valid only for the time of the session. With such a limited object, it’s impossible to emulate java.io.File, the object returned by the JavaFX FileChooser.

The alternative WebFX cross-platform API

While it looks impossible to emulate the JavaFX FileChooser and the Java File API in the browser, the opposite is possible: it’s quite simple to emulate the Web FilePicker and Web File API in Java/JavaFX. This is therefore the WebFX approach.

In our WebFX Platform, you will see these 3 new modules:

  • webfx-platform-file : our File API (dev.webfx.platform.file.*)
  • webfx-platform-file-java : Java implementation (using Java File)
  • webfx-platform-file-gwt : GWT implementation (using Web File)

And in our WebFX Extras library, these 3 new modules:

  • webfx-extras-filepicker: our FilePicker API (dev.webfx.extras.filepicker.*)
  • webfx-extras-filepicker-openjfx : OpenJFX implementation (using FileChooser)
  • webfx-extras-filepicker-gwt : GWT implementation (using Web FilePicker)

Including this new API in your project

Assuming you already followed our getting started guide, just add this line in the <required-libraries> section of the webfx.xml located at your project’s root directory:

1
2
3
<required-libraries>
<webfx-library artifact="dev.webfx:webfx-extras:0.1.0-SNAPSHOT"/>
</required-libraries>

This will make your WebFX CLI aware of the WebFX Extras library (no need to do it for the WebFX platform library, as it’s a transitive dependency). Then, each time it sees packages from WebFX Extras or WebFX Platform libraries in your code, it will automatically add the associated module(s).

For example, if you add the following import to one of your Java files:

1
import dev.webfx.extras.filepicker.*;

or, alternatively, temporarily use a fully qualified class name in your code:

1
2
3
4
5
public class AClass {
...
dev.webfx.extras.filepicker.FilePicker filePicker;
...
}

and then invoke the update command in your terminal (beside your webfx.xml):

1
webfx update

It will recognize you are using the webfx-extras-filepicker module, and update the build chain for you (Maven, GWT, etc…). You are now ready to use the API in your Java IDE!

Ok great, now show me the code!

You can find the complete source code of the demo on GitHub, but we will concentrate here on the essential parts. To start with, here is how to use the WebFX FilePicker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Creating an instance of FilePicker
FilePicker filePicker = FilePicker.create(); // OpenJFX or GWT instance
// Customizing the FilePicker button appearance
filePicker.setGraphic(myBeautifulJavaFXNode);
// Allowing multiple file selection
filePicker.setMultiple(true);
// Inserting the FilePicker button in the UI
myBorderPane.setTop(filePicker.getView()); // Just as an example
// Reacting to the user files selection
filePicker.getSelectedFiles().addListener((InvalidationListener) obs -> {
// Getting the selected files
List<File> fileList = filePicker.getSelectedFiles();
... // Your code treating these files
});

As you can see, there is no programmatic call to open the FilePicker. You get your files simply by listening getSelectedFiles() which returns an ObservableList<File> which is updated on user selection. In case of single selection mode, you can alternatively use getSelectedFile() which returns a ReadOnlyObjectProperty<File>.

The GWT implementation of FilePicker.getView() will show your node instead of the ugly default button - which is still present in the HTML page by the way to make things work, but hidden behind your beautiful JavaFX node with some CSS tricks.

For the drag & drop support, your code will typically look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// When the user drags files over that node,
// we tell the system we are accepting them
myDragAndDropZoneNode.setOnDragOver(e -> {
if (e.getDragboard().hasContent(DataFormat.FILES)) {
e.acceptTransferModes(TransferMode.COPY);
... // Good to do a visual change here
... // such as changing the border of the node
}
});
// When the user finally drops the files on that node, we get them
myDragAndDropZoneNode.setOnDragDropped(e -> {
Dragboard db = e.getDragboard();
if (db.hasContent(DataFormat.FILES)) {
Object platformFiles = db.getContent(DataFormat.FILES);
List<File> fileList = File.createFileList(platformFiles);
... // Your logic to do what you need to do with these files
}
});

Please note that the object returned by db.getContent(DataFormat.FILES) is a native list of files from the underlying platform, so not usable “as-is” in your code. However, you can transform it into a cross-platform list of files by calling File.createFileList().

Here is what you can now do with the WebFX File instances:

1
2
3
4
5
6
7
8
9
10
// Getting the file name (doesn't include the folder path)
String fileName = file.getName();
// Getting the file length in bytes
long fileLength = file.length();
// Getting the last modified date & time in Epoch milliseconds
long fileLastModified = file.lastModified();
// Getting the MIME type of the file
String mimeType = file.getMimeType();
// Getting an object URL to reference the contents of the file
String objectURL = file.getObjectURL();

The GWT version of File.getObjectURL() calls the Web API URL.createObjectURL() which returns a sandboxed URL that can be used to open the file as follows (the same code also works of course on the Java/JavaFX platform):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*** IMAGE FILES (MIME type starting with "image/") ***/
ImageView imageView = new ImageView(file.getObjectURL());
// Alternatively using Image to control the background loading
Image image = new Image(file.getURLPath(), true); // backgroundLoading
ImageView imageView = new ImageView(image);

/*** AUDIO & VIDEOS (MIME type starting with "audio/" & "video/") ***/
Media media = new Media(file.getObjectURL());
MediaPlayer mediaPlayer = new MediaPlayer(media);
mediaPlayer.play(); // This will play only the sound

/*** TEXT FILES (MIME type starting with "text/") ***/
TextArea textArea = new TextArea();
FileReader.create() // FileReader is also part of the WebFX File API
.readAsText(file) // Asynchronous call
.onSuccess(textArea::setText); // Successful callback

What’s next?

We will soon provide the video support, so you can play your video files as follows:

1
2
3
4
5
Media media = new Media(videoFile.getObjectURL());
MediaPlayer mediaPlayer = new MediaPlayer(media);
MediaView mediaView = new MediaView(mediaPlayer);
myBorderPane.setCenter(mediaView);
mediaPlayer.play();

For your info, the Web File API we just investigated here is the standard API supported by all browsers. There is another one called the File System Access API which is experimental (only Chromium-based browsers support it for now). But it is much more extensive, including allowing files to be saved back to the local device.

Any question or comment? You are very welcome to post it in this GitHub discussion dedicated to this topic. In particular if you would like WebFX to support the experimental File System Access API, let us know, and we will investigate further.

Thank you for reading!