]> source.dussan.org Git - vaadin-framework.git/commitdiff
Make it possible to upload files by dropping them onto a drop target (#9277)
authorAdam Wagner <wbadam@users.noreply.github.com>
Thu, 11 May 2017 10:13:10 +0000 (13:13 +0300)
committerPekka Hyvönen <pekka@vaadin.com>
Thu, 11 May 2017 10:13:10 +0000 (13:13 +0300)
Fixes #8891

client/src/main/java/com/vaadin/client/extensions/FileDropTargetConnector.java [new file with mode: 0644]
documentation/advanced/advanced-dragndrop.asciidoc
server/src/main/java/com/vaadin/event/dnd/FileDropEvent.java [new file with mode: 0644]
server/src/main/java/com/vaadin/event/dnd/FileDropHandler.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/FileDropTarget.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetClientRpc.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetRpc.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/dnd/FileDropTargetState.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/dnd/FileParameters.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/dnd/Html5FileDragAndDropUpload.java [new file with mode: 0644]

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 (file)
index 0000000..f496e90
--- /dev/null
@@ -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);
+        }-*/;
+
+    }
+}
index 57764b55ddc913520180f74cf74d796e659a54a7..6390eff201a61c49c35ec10d7b736dc4a56f8fcd 100644 (file)
@@ -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 (file)
index 0000000..42d270e
--- /dev/null
@@ -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 (file)
index 0000000..5e74e44
--- /dev/null
@@ -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 (file)
index 0000000..654563d
--- /dev/null
@@ -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 (file)
index 0000000..479e026
--- /dev/null
@@ -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 (file)
index 0000000..fd290a4
--- /dev/null
@@ -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 (file)
index 0000000..d825a4d
--- /dev/null
@@ -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 (file)
index 0000000..0e4f5cd
--- /dev/null
@@ -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 (file)
index 0000000..211d50d
--- /dev/null
@@ -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";
+    }
+}