diff options
author | Adam Wagner <wbadam@users.noreply.github.com> | 2017-05-11 13:13:10 +0300 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2017-05-11 13:13:10 +0300 |
commit | e2e3058a497f43f34f2fcfadf6b63de9211be659 (patch) | |
tree | 265632d4dfce4edb0fc15365e4f0339333dbcce3 /server | |
parent | a4ffc1e1597e2ce3a3ac2977458c8df25e112c88 (diff) | |
download | vaadin-framework-e2e3058a497f43f34f2fcfadf6b63de9211be659.tar.gz vaadin-framework-e2e3058a497f43f34f2fcfadf6b63de9211be659.zip |
Make it possible to upload files by dropping them onto a drop target (#9277)
Fixes #8891
Diffstat (limited to 'server')
3 files changed, 368 insertions, 0 deletions
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() { + + } + } + + } +} |