summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Wagner <wbadam@users.noreply.github.com>2017-05-11 13:13:10 +0300
committerPekka Hyvönen <pekka@vaadin.com>2017-05-11 13:13:10 +0300
commite2e3058a497f43f34f2fcfadf6b63de9211be659 (patch)
tree265632d4dfce4edb0fc15365e4f0339333dbcce3
parenta4ffc1e1597e2ce3a3ac2977458c8df25e112c88 (diff)
downloadvaadin-framework-e2e3058a497f43f34f2fcfadf6b63de9211be659.tar.gz
vaadin-framework-e2e3058a497f43f34f2fcfadf6b63de9211be659.zip
Make it possible to upload files by dropping them onto a drop target (#9277)
Fixes #8891
-rw-r--r--client/src/main/java/com/vaadin/client/extensions/FileDropTargetConnector.java202
-rw-r--r--documentation/advanced/advanced-dragndrop.asciidoc64
-rw-r--r--server/src/main/java/com/vaadin/event/dnd/FileDropEvent.java62
-rw-r--r--server/src/main/java/com/vaadin/event/dnd/FileDropHandler.java44
-rw-r--r--server/src/main/java/com/vaadin/ui/FileDropTarget.java262
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetClientRpc.java37
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetRpc.java37
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetState.java26
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/FileParameters.java110
-rw-r--r--uitest/src/main/java/com/vaadin/tests/dnd/Html5FileDragAndDropUpload.java164
10 files changed, 1008 insertions, 0 deletions
diff --git a/client/src/main/java/com/vaadin/client/extensions/FileDropTargetConnector.java b/client/src/main/java/com/vaadin/client/extensions/FileDropTargetConnector.java
new file mode 100644
index 0000000000..f496e90d7f
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/extensions/FileDropTargetConnector.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.extensions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.DataTransfer;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.xhr.client.XMLHttpRequest;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.dnd.FileDropTargetClientRpc;
+import com.vaadin.shared.ui.dnd.FileDropTargetRpc;
+import com.vaadin.shared.ui.dnd.FileDropTargetState;
+import com.vaadin.shared.ui.dnd.FileParameters;
+import com.vaadin.ui.FileDropTarget;
+
+import elemental.events.Event;
+import elemental.html.File;
+import elemental.html.FileList;
+
+/**
+ * Extension to add file drop target functionality to a widget. It allows
+ * dropping files onto the widget and uploading the dropped files to the server.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+@Connect(FileDropTarget.class)
+public class FileDropTargetConnector extends DropTargetExtensionConnector {
+
+ /**
+ * Contains files and their IDs that are waiting to be uploaded.
+ */
+ private Map<String, File> filesToUpload = new HashMap<>();
+
+ /**
+ * Contains file IDs and upload URLs.
+ */
+ private Map<String, String> uploadUrls = new HashMap<>();
+
+ /**
+ * Counting identifier for the files to be uploaded.
+ */
+ private int fileId = 0;
+
+ /**
+ * Indicates whether a file is being uploaded.
+ */
+ private boolean uploading = false;
+
+ /**
+ * Constructs file drop target connector.
+ */
+ public FileDropTargetConnector() {
+ registerRpc(FileDropTargetClientRpc.class,
+ (FileDropTargetClientRpc) urls -> {
+ uploadUrls.putAll(urls);
+ uploadNextFile();
+ });
+ }
+
+ /**
+ * Uploads a file from the waiting list in case there are no files being
+ * uploaded.
+ */
+ private void uploadNextFile() {
+ Scheduler.get().scheduleDeferred(() -> {
+ if (!uploading && uploadUrls.size() > 0) {
+ uploading = true;
+ String nextId = uploadUrls.keySet().stream().findAny().get();
+
+ String url = uploadUrls.remove(nextId);
+ File file = filesToUpload.remove(nextId);
+
+ FileUploadXHR xhr = (FileUploadXHR) FileUploadXHR.create();
+ xhr.setOnReadyStateChange(xmlHttpRequest -> {
+ if (xmlHttpRequest.getReadyState() == XMLHttpRequest.DONE) {
+ uploading = false;
+ uploadNextFile();
+ xmlHttpRequest.clearOnReadyStateChange();
+ }
+ });
+ xhr.open("POST", getConnection().translateVaadinUri(url));
+ xhr.postFile(file);
+ }
+ });
+ }
+
+ @Override
+ protected void onDrop(Event event) {
+ DataTransfer dataTransfer = ((NativeEvent) event).getDataTransfer();
+ FileList files = getFiles(dataTransfer);
+
+ if (files != null) {
+ Map<String, FileParameters> fileParams = new HashMap<>();
+ for (int i = 0; i < files.getLength(); i++) {
+ File file = files.item(i);
+
+ // Make sure the item is indeed a file and not a folder
+ if (isFile(file, i, dataTransfer)) {
+ String id = String.valueOf(++this.fileId);
+
+ filesToUpload.put(id, file);
+ fileParams.put(id, new FileParameters(file.getName(),
+ (long) file.getSize(), file.getType()));
+ }
+ }
+
+ // Request a list of upload URLs for the dropped files
+ if (fileParams.size() > 0) {
+ getRpcProxy(FileDropTargetRpc.class).drop(fileParams);
+ }
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ @Override
+ public FileDropTargetState getState() {
+ return (FileDropTargetState) super.getState();
+ }
+
+ /**
+ * Returns the files parameter of the dataTransfer object.
+ *
+ * @param dataTransfer
+ * DataTransfer object to retrieve files from.
+ * @return {@code DataTransfer.files} parameter of the given dataTransfer
+ * object.
+ */
+ private native FileList getFiles(DataTransfer dataTransfer)
+ /*-{
+ return dataTransfer.files;
+ }-*/;
+
+ /**
+ * Checks whether the file on the given index is indeed a file or a folder.
+ *
+ * @param file
+ * File object to prove it is not a folder.
+ * @param fileIndex
+ * Index of the file object.
+ * @param dataTransfer
+ * DataTransfer object that contains the list of files.
+ * @return {@code true} if the given file at the given index is not a
+ * folder, {@code false} otherwise.
+ */
+ private native boolean isFile(File file, int fileIndex,
+ DataTransfer dataTransfer)
+ /*-{
+ // Chrome >= v21 and Opera >= v?
+ if (dataTransfer.items) {
+ var item = dataTransfer.items[fileIndex];
+ if (typeof item.webkitGetAsEntry == "function") {
+ var entry = item.webkitGetAsEntry();
+ if (typeof entry !== "undefined" && entry !== null) {
+ return entry.isFile;
+ }
+ }
+ }
+
+ // Zero sized files without a type are also likely to be folders
+ if (file.size == 0 && !file.type) {
+ return false;
+ }
+
+ // TODO Make it detect folders on all browsers
+
+ return true;
+ }-*/;
+
+ /**
+ * XHR that is used for uploading a file to the server.
+ */
+ private static class FileUploadXHR extends XMLHttpRequest {
+
+ protected FileUploadXHR() {
+ }
+
+ public final native void postFile(File file) /*-{
+ this.setRequestHeader('Content-Type', 'multipart/form-data');
+ this.send(file);
+ }-*/;
+
+ }
+}
diff --git a/documentation/advanced/advanced-dragndrop.asciidoc b/documentation/advanced/advanced-dragndrop.asciidoc
index 57764b55dd..6390eff201 100644
--- a/documentation/advanced/advanced-dragndrop.asciidoc
+++ b/documentation/advanced/advanced-dragndrop.asciidoc
@@ -287,4 +287,68 @@ When dragging data over a drop target Grid's row, depending on the drop mode and
(((range="endofrange", startref="term.advanced.dragndrop")))
+== Drag and Drop Files
+Files can be uploaded to the server by dropping them onto a file drop target. To make a component a file drop target, apply the [classname]#FileDropTarget# extension to it by creating a new instance and passing the component as first constructor parameter to it.
+
+You can handle the dropped files with the `FileDropHandler` that you add as the second constructor parameter. The [classname]#FileDropEvent#, received by the handler, contains information about the dropped files such as file name, file size and mime type.
+In the handler you can decide if you would like to upload each of the dropped files.
+
+To start uploading a file, set a `StreamVariable` to it. The stream variable provides an output stream where the file will be written and has callback methods for all the stages of the upload process.
+
+[source,java]
+----
+Label dropArea = new Label("Drop files here");
+FileDropTarget<Label> dropTarget = new FileDropTarget<>(dropArea, event -> {
+
+ List<Html5File> files = event.getFiles();
+ files.forEach(file -> {
+ // Max 1 MB files are uploaded
+ if (file.getFileSize() <= 1024 * 1024) {
+ file.setStreamVariable(new StreamVariable() {
+
+ // Output stream to write the file to
+ @Override
+ public OutputStream getOutputStream() {
+ return new FileOutputStream("/path/to/files/"
+ + file.getFileName());
+ }
+
+ // Returns whether onProgress() is called during upload
+ @Override
+ public boolean listenProgress() {
+ return true;
+ }
+
+ // Called periodically during upload
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ Notification.show("Progress, bytesReceived="
+ + event.getBytesReceived());
+ }
+
+ // Called when upload started
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ Notification.show("Stream started, fileName="
+ + event.getFileName());
+ }
+
+ // Called when upload finished
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ Notification.show("Stream finished, fileName="
+ + event.getFileName());
+ }
+
+ // Called when upload failed
+ @Override
+ public void streamingFailed(StreamingErrorEvent event) {
+ Notification.show("Stream failed, fileName="
+ + event.getFileName());
+ }
+ });
+ }
+ }
+});
+----
diff --git a/server/src/main/java/com/vaadin/event/dnd/FileDropEvent.java b/server/src/main/java/com/vaadin/event/dnd/FileDropEvent.java
new file mode 100644
index 0000000000..42d270ebe4
--- /dev/null
+++ b/server/src/main/java/com/vaadin/event/dnd/FileDropEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.event.dnd;
+
+import java.util.List;
+
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Html5File;
+
+/**
+ * File drop event that contains the list of files dropped on a file drop
+ * target.
+ *
+ * @param <T>
+ * Type of the file drop target component.
+ * @author Vaadin Ltd
+ * @see FileDropHandler
+ * @since 8.1
+ */
+public class FileDropEvent<T extends AbstractComponent> extends
+ Component.Event {
+
+ private final List<Html5File> files;
+
+ /**
+ * Creates a file drop event.
+ *
+ * @param target
+ * The file drop target component.
+ * @param files
+ * List of files.
+ */
+ public FileDropEvent(T target, List<Html5File> files) {
+ super(target);
+
+ this.files = files;
+ }
+
+ /**
+ * Gets the list of files dropped onto the file drop target component.
+ *
+ * @return List of files that were dropped onto the file drop target
+ * component.
+ */
+ public List<Html5File> getFiles() {
+ return files;
+ }
+}
diff --git a/server/src/main/java/com/vaadin/event/dnd/FileDropHandler.java b/server/src/main/java/com/vaadin/event/dnd/FileDropHandler.java
new file mode 100644
index 0000000000..5e74e44349
--- /dev/null
+++ b/server/src/main/java/com/vaadin/event/dnd/FileDropHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.event.dnd;
+
+import java.io.Serializable;
+
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * Handles the drop event on a file drop target.
+ *
+ * @param <T>
+ * Type of the file drop target component.
+ * @author Vaadin Ltd
+ * @see FileDropEvent
+ * @see com.vaadin.ui.FileDropTarget
+ * @since 8.1
+ */
+public interface FileDropHandler<T extends AbstractComponent> extends
+ Serializable {
+
+ /**
+ * Handles the drop event. The method is called when files are dropped onto
+ * the file drop target this handler is registered to.
+ *
+ * @param event
+ * The file drop event containing the list of files that were
+ * dropped onto the component.
+ */
+ public void drop(FileDropEvent<T> event);
+}
diff --git a/server/src/main/java/com/vaadin/ui/FileDropTarget.java b/server/src/main/java/com/vaadin/ui/FileDropTarget.java
new file mode 100644
index 0000000000..654563dbd0
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/FileDropTarget.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.event.dnd.DropTargetExtension;
+import com.vaadin.event.dnd.FileDropEvent;
+import com.vaadin.event.dnd.FileDropHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.StreamVariable;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.ui.dnd.FileDropTargetClientRpc;
+import com.vaadin.shared.ui.dnd.FileDropTargetRpc;
+import com.vaadin.shared.ui.dnd.FileDropTargetState;
+
+/**
+ * Extension to add drop target functionality to a widget for accepting and
+ * uploading files.
+ *
+ * @param <T>
+ * Type of the component to be extended.
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public class FileDropTarget<T extends AbstractComponent> extends
+ DropTargetExtension<T> {
+
+ /**
+ * Handles the file drop event.
+ */
+ private final FileDropHandler<T> fileDropHandler;
+
+ /**
+ * Extends {@code target} component and makes it a file drop target. A file
+ * drop handler needs to be added to handle the file drop event.
+ *
+ * @param target
+ * Component to be extended.
+ * @param fileDropHandler
+ * File drop handler that handles the file drop event.
+ * @see FileDropEvent
+ */
+ public FileDropTarget(T target, FileDropHandler<T> fileDropHandler) {
+ super(target);
+
+ this.fileDropHandler = fileDropHandler;
+ }
+
+ @Override
+ protected void registerDropTargetRpc(T target) {
+ super.registerDropTargetRpc(target);
+
+ registerRpc((FileDropTargetRpc) fileParams -> {
+
+ List<Html5File> files = new ArrayList<>();
+ Map<String, String> urls = new HashMap<>();
+
+ fileParams.forEach((id, fileParameters) -> {
+ Html5File html5File = new Html5File(fileParameters.getName(),
+ fileParameters.getSize(), fileParameters.getMime());
+ String url = createUrl(html5File, id);
+
+ files.add(html5File);
+ urls.put(id, url);
+ });
+
+ getRpcProxy(FileDropTargetClientRpc.class).sendUploadUrl(urls);
+
+ FileDropEvent<T> event = new FileDropEvent<>(target, files);
+ fileDropHandler.drop(event);
+ });
+ }
+
+ /**
+ * Creates an upload URL for the given file and file ID.
+ *
+ * @param file
+ * File to be uploaded.
+ * @param id
+ * Generated ID for the file.
+ * @return Upload URL for uploading the file to the server.
+ */
+ private String createUrl(Html5File file, String id) {
+ return getStreamVariableTargetUrl("rec-" + id,
+ new FileReceiver(id, file));
+ }
+
+ private String getStreamVariableTargetUrl(String name,
+ StreamVariable value) {
+ String connectorId = getConnectorId();
+ UI ui = getUI();
+ int uiId = ui.getUIId();
+ String key = uiId + "/" + connectorId + "/" + name;
+
+ ConnectorTracker connectorTracker = ui.getConnectorTracker();
+ connectorTracker.addStreamVariable(connectorId, name, value);
+ String secKey = connectorTracker.getSeckey(value);
+
+ return ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ServletPortletHelper.UPLOAD_URL_PREFIX + key + "/" + secKey;
+ }
+
+ @Override
+ protected FileDropTargetState getState() {
+ return (FileDropTargetState) super.getState();
+ }
+
+
+ @Override
+ protected FileDropTargetState getState(boolean markAsDirty) {
+ return (FileDropTargetState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Returns the component this extension is attached to.
+ *
+ * @return Extended component.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getParent() {
+ return (T) super.getParent();
+ }
+
+ private class FileReceiver implements StreamVariable {
+
+ private final String id;
+ private Html5File file;
+
+ public FileReceiver(String id, Html5File file) {
+ this.id = id;
+ this.file = file;
+ }
+
+ private boolean listenProgressOfUploadedFile;
+
+ @Override
+ public OutputStream getOutputStream() {
+ if (file.getStreamVariable() == null) {
+ return null;
+ }
+ return file.getStreamVariable().getOutputStream();
+ }
+
+ @Override
+ public boolean listenProgress() {
+ return file.getStreamVariable().listenProgress();
+ }
+
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ file.getStreamVariable()
+ .onProgress(new ReceivingEventWrapper(event));
+ }
+
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ listenProgressOfUploadedFile = file.getStreamVariable() != null;
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable()
+ .streamingStarted(new ReceivingEventWrapper(event));
+ }
+ }
+
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable()
+ .streamingFinished(new ReceivingEventWrapper(event));
+ }
+ }
+
+ @Override
+ public void streamingFailed(final StreamingErrorEvent event) {
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable()
+ .streamingFailed(new ReceivingEventWrapper(event));
+ }
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return file.getStreamVariable().isInterrupted();
+ }
+
+ /*
+ * With XHR2 file posts we can't provide as much information from the
+ * terminal as with multipart request. This helper class wraps the
+ * terminal event and provides the lacking information from the
+ * FileParameters.
+ */
+ class ReceivingEventWrapper implements StreamingErrorEvent,
+ StreamingEndEvent, StreamingStartEvent, StreamingProgressEvent {
+
+ private final StreamingEvent wrappedEvent;
+
+ ReceivingEventWrapper(StreamingEvent e) {
+ wrappedEvent = e;
+ }
+
+ @Override
+ public String getMimeType() {
+ return file.getType();
+ }
+
+ @Override
+ public String getFileName() {
+ return file.getFileName();
+ }
+
+ @Override
+ public long getContentLength() {
+ return file.getFileSize();
+ }
+
+ public StreamVariable getReceiver() {
+ return FileReceiver.this;
+ }
+
+ @Override
+ public Exception getException() {
+ if (wrappedEvent instanceof StreamingErrorEvent) {
+ return ((StreamingErrorEvent) wrappedEvent).getException();
+ }
+ return null;
+ }
+
+ @Override
+ public long getBytesReceived() {
+ return wrappedEvent.getBytesReceived();
+ }
+
+ /**
+ * Calling this method has no effect. DD files are receive only once
+ * anyway.
+ */
+ @Override
+ public void disposeStreamVariable() {
+
+ }
+ }
+
+ }
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetClientRpc.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetClientRpc.java
new file mode 100644
index 0000000000..479e026cf7
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetClientRpc.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.dnd;
+
+import java.util.Map;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * RPC for sending the upload URLs to the client for uploading files.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public interface FileDropTargetClientRpc extends ClientRpc {
+
+ /**
+ * Sends the of upload URLs mapped to the generated file ID.
+ *
+ * @param urls
+ * File IDs and URLs for uploading files to the server.
+ */
+ void sendUploadUrl(Map<String, String> urls);
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetRpc.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetRpc.java
new file mode 100644
index 0000000000..fd290a41f5
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetRpc.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.dnd;
+
+import java.util.Map;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC for requesting upload URLs for files dropped on the file drop target.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public interface FileDropTargetRpc extends ServerRpc {
+
+ /**
+ * Called when files are dropped onto the file drop target.
+ *
+ * @param fileParams
+ * Generated file IDs and file parameters of dropped files.
+ */
+ public void drop(Map<String, FileParameters> fileParams);
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetState.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetState.java
new file mode 100644
index 0000000000..d825a4d60f
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetState.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.dnd;
+
+/**
+ * State class containing parameters for FileDropTarget.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public class FileDropTargetState extends DropTargetState {
+
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/FileParameters.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileParameters.java
new file mode 100644
index 0000000000..0e4f5cdf76
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/FileParameters.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.dnd;
+
+import java.io.Serializable;
+
+/**
+ * Contains parameters of a file. Used for transferring information about
+ * dropped files from the client to the server.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public class FileParameters implements Serializable {
+ private String name;
+ private long size;
+ private String mime;
+
+ /**
+ * Creates a file parameters object.
+ */
+ public FileParameters() {
+ }
+
+ /**
+ * Creates a file parameters object.
+ *
+ * @param name
+ * Name of the file.
+ * @param size
+ * Size of the file.
+ * @param mime
+ * Mime type of the file.
+ */
+ public FileParameters(String name, long size, String mime) {
+ this.name = name;
+ this.size = size;
+ this.mime = mime;
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return Name of the file.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the file name.
+ *
+ * @param name
+ * Name of the file.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the file size.
+ *
+ * @return Size of the file.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the file size.
+ *
+ * @param size
+ * Size of the file.
+ */
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ /**
+ * Gets the mime type.
+ *
+ * @return Mime type of the file.
+ */
+ public String getMime() {
+ return mime;
+ }
+
+ /**
+ * Sets the mime type.
+ *
+ * @param mime
+ * Mime type of the file.
+ */
+ public void setMime(String mime) {
+ this.mime = mime;
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/dnd/Html5FileDragAndDropUpload.java b/uitest/src/main/java/com/vaadin/tests/dnd/Html5FileDragAndDropUpload.java
new file mode 100644
index 0000000000..211d50d047
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/dnd/Html5FileDragAndDropUpload.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.dnd;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.server.StreamVariable;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.dnd.FileParameters;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.FileDropTarget;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Html5File;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.VerticalLayout;
+
+public class Html5FileDragAndDropUpload extends AbstractTestUIWithLog {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+
+ Label dropArea = new Label("Drop files here");
+
+ FileDropTarget<Label> dropTarget = new FileDropTarget<>(dropArea,
+ event -> {
+ List<Html5File> files = event.getFiles();
+ if (files != null) {
+ files.forEach(file -> {
+ file.setStreamVariable(new StreamVariable() {
+ @Override
+ public OutputStream getOutputStream() {
+ return new OutputStream() {
+ @Override
+ public void write(int b) throws
+ IOException {
+ // NOP
+ }
+ };
+ }
+
+ @Override
+ public boolean listenProgress() {
+ return true;
+ }
+
+ @Override
+ public void onProgress(
+ StreamingProgressEvent event) {
+ log("Progress, bytesReceived=" + event
+ .getBytesReceived());
+ }
+
+ @Override
+ public void streamingStarted(
+ StreamingStartEvent event) {
+ log("Stream started, fileName=" + event
+ .getFileName());
+ }
+
+ @Override
+ public void streamingFinished(
+ StreamingEndEvent event) {
+ log("Stream finished, fileName=" + event
+ .getFileName());
+ }
+
+ @Override
+ public void streamingFailed(
+ StreamingErrorEvent event) {
+ log("Stream failed, fileName=" + event
+ .getFileName());
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return false;
+ }
+ });
+ });
+ }
+ });
+
+ Grid<FileParameters> grid = new Grid<>();
+ grid.addColumn(FileParameters::getName).setCaption("File name");
+ grid.addColumn(FileParameters::getSize).setCaption("File size");
+ grid.addColumn(FileParameters::getMime).setCaption("Mime type");
+
+ List<FileParameters> gridItems = new ArrayList<>();
+
+ new FileDropTarget<Grid<FileParameters>>(grid, event -> {
+ event.getFiles().forEach(html5File -> {
+ html5File.setStreamVariable(new StreamVariable() {
+ @Override
+ public OutputStream getOutputStream() {
+ return new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // NOP
+ }
+ };
+ }
+
+ @Override
+ public boolean listenProgress() {
+ return false;
+ }
+
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ // NOP
+ }
+
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ // NOP
+ }
+
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ gridItems.add(new FileParameters(event.getFileName(),
+ event.getContentLength(), event.getMimeType()));
+ grid.setItems(gridItems);
+ }
+
+ @Override
+ public void streamingFailed(StreamingErrorEvent event) {
+
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return false;
+ }
+ });
+ });
+ });
+
+ Layout layout = new VerticalLayout(dropArea, grid);
+
+ addComponent(layout);
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "Drop and upload files onto file drop target";
+ }
+}