Browse Source

A proper (FF36 only) file drag support, enhanced test case. Should be forward compatible.

svn changeset:11835/svn branch:6.3
tags/6.7.0.beta1
Matti Tahvonen 14 years ago
parent
commit
f1d07c8984

+ 98
- 34
src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java View File

@@ -13,6 +13,7 @@ import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xhr.client.XMLHttpRequest;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.Paintable;
@@ -193,44 +194,16 @@ public class VDragAndDropWrapper extends VCustomComponent implements
transferable.setData("filecount", fileCount);
for (int i = 0; i < fileCount; i++) {
final int fileId = filecounter++;
final VHtml5File file = event.getFile(fileCount);
transferable.setData("fn" + fileId, file.getName());
transferable.setData("ft" + fileId, file.getType());
transferable.setData("fs" + fileId, file.getSize());
DeferredCommand.addCommand(new Command() {
public void execute() {
/*
* File contents is sent deferred to allow quick
* reaction on GUI although file upload may last long.
* TODO make this use apache file upload instead of our
* variable post like in upload. Currently stalls the
* GUI during upload. Also need to use dataurl to
* support all possible bytes in file content
*/
file.readAsDataUrl(new Callback() {
public void handleFile(JavaScriptObject object) {
client.updateVariable(client
.getPid(VDragAndDropWrapper.this),
"file" + fileId, object.toString(),
true);

}
});

}
});
final VHtml5File file = event.getFile(i);
transferable.setData("fi" + i, "" + fileId);
transferable.setData("fn" + i, file.getName());
transferable.setData("ft" + i, file.getType());
transferable.setData("fs" + i, file.getSize());
postFile(fileId, file);
}

}

// TODO remove this when above cleaner and more standard compliance
// system works
String fileAsString = event.getFileAsString(0);
if (fileAsString != null) {
ApplicationConnection.getConsole().log(fileAsString);
transferable.setData("fileContents", fileAsString);
}

VDragAndDropManager.get().endDrag();
vaadinDragEvent = null;
event.preventDefault();
@@ -239,6 +212,97 @@ public class VDragAndDropWrapper extends VCustomComponent implements
return false;
}

static class ExtendedXHR extends XMLHttpRequest {

protected ExtendedXHR() {
}

public final native void sendBinary(JavaScriptObject data)
/*-{
//this.overrideMimeType('text/plain; charset=x-user-defined-binary');
this.sendAsBinary(data);
}-*/;

}

/**
*
* Currently supports only FF36 as no other browser supprots natively File
* api.
*
* @param fileId
* @param data
*/
private void postFile(final int fileId, final VHtml5File file) {
DeferredCommand.addCommand(new Command() {
public void execute() {
/*
* File contents is sent deferred to allow quick reaction on GUI
* although file upload may last long. TODO make this use apache
* file upload instead of our variable post like in upload.
* Currently stalls the GUI during upload. Also need to use
* dataurl to support all possible bytes in file content
*/
file.readAsBinary(new Callback() {
public void handleFile(final JavaScriptObject object) {

DeferredCommand.addCommand(new Command() {

public void execute() {

ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
.create();
extendedXHR.open("POST", client.getAppUri());
extendedXHR
.setRequestHeader(
"PaintableId",
client
.getPid(VDragAndDropWrapper.this));
extendedXHR.setRequestHeader("FileId", ""
+ fileId);

// extendedXHR.setRequestHeader("Connection",
// "close");

multipartSend(
extendedXHR,
object,
"XHRFILE"
+ client
.getPid(VDragAndDropWrapper.this)
+ "." + fileId);

}
});
}
});

}
});

}

private native void multipartSend(JavaScriptObject xhr,
JavaScriptObject data, String name)
/*-{
var boundaryString = "------------------------------------------VAADINXHRFILEUPLOAD";
var boundary = "--" + boundaryString;
var CRLF = "\r\n";
xhr.setRequestHeader("Content-type", "multipart/form-data; boundary=\"" + boundaryString + "\"");
var requestBody = boundary
+ CRLF
+ "Content-Disposition: form-data; name=\""+name+"\"; filename=\"file\""
+ CRLF
+ "Content-Type: application/octet-stream" // hard coded, type sent separately
+ CRLF + CRLF + data.target.result + CRLF + boundary + "--" + CRLF;
xhr.setRequestHeader("Content-Length", requestBody.length);
xhr.sendAsBinary(requestBody);
}-*/;

public VDropHandler getDropHandler() {
return dropHandler;
}

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent.java View File

@@ -59,7 +59,7 @@ public class VHtml5DragEvent extends NativeEvent {

public final native VHtml5File getFile(int fileIndex)
/*-{
return this.dataTransfer.files[i];
return this.dataTransfer.files[fileIndex];
}-*/;

}

+ 6
- 4
src/com/vaadin/terminal/gwt/client/ui/dd/VHtml5File.java View File

@@ -14,17 +14,17 @@ public class VHtml5File extends JavaScriptObject {

public native final String getName()
/*-{
return name;
return this.name;
}-*/;

public native final String getType()
/*-{
return type;
return this.type;
}-*/;

public native final int getSize()
/*-{
return size;
return this.size;
}-*/;

public native final void readAsBinary(final Callback callback)
@@ -33,7 +33,9 @@ public class VHtml5File extends JavaScriptObject {
r.onloadend = function(content) {
callback.@com.vaadin.terminal.gwt.client.ui.dd.VHtml5File.Callback::handleFile(Lcom/google/gwt/core/client/JavaScriptObject;)(content);
};
r.readAsBinary(this);
r.readAsBinaryString(this);
var j = 0;
}-*/;

public native final void readAsDataUrl(final Callback callback)

+ 2
- 0
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java View File

@@ -1252,6 +1252,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
return RequestType.STATIC_FILE;
} else if (isApplicationRequest(request)) {
return RequestType.APPLICATION_RESOURCE;
} else if (request.getHeader("FileId") != null) {
return RequestType.FILE_UPLOAD;
}
return RequestType.OTHER;


+ 47
- 28
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java View File

@@ -62,6 +62,7 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Component;
import com.vaadin.ui.DragAndDropWrapper;
import com.vaadin.ui.Upload;
import com.vaadin.ui.Window;
import com.vaadin.ui.Upload.UploadException;
@@ -359,6 +360,7 @@ public abstract class AbstractCommunicationManager implements
*/
protected void doHandleFileUpload(Request request, Response response)
throws IOException, FileUploadException {

// Create a new file upload handler
final FileUpload upload = createFileUpload();

@@ -386,22 +388,6 @@ public abstract class AbstractCommunicationManager implements
if (item.isFormField()) {
// ignored, upload requests contains only files
} else {
int separatorPos = name.lastIndexOf("_");
final String pid = name.substring(0, separatorPos);
final Upload uploadComponent = (Upload) idPaintableMap
.get(pid);
if (uploadComponent == null) {
throw new FileUploadException(
"Upload component not found");
}
if (uploadComponent.isReadOnly()) {
throw new FileUploadException(
"Warning: ignored file upload because upload component is set as read-only");
}
synchronized (application) {
// put upload component into receiving state
uploadComponent.startUpload();
}
final UploadStream upstream = new UploadStream() {

public String getContentName() {
@@ -422,22 +408,55 @@ public abstract class AbstractCommunicationManager implements

};

// tell UploadProgressListener which component is receiving
// file
pl.setUpload(uploadComponent);
if (name.startsWith("XHRFILE")) {
String[] split = item.getFieldName().substring(7)
.split("\\.");
DragAndDropWrapper ddw = (DragAndDropWrapper) idPaintableMap
.get(split[0]);
ddw.receiveFile(upstream, split[1]);

try {
uploadComponent.receiveUpload(upstream);
} catch (UploadException e) {
// error happened while receiving file. Handle the
// error in the same manner as it would have happened in
// variable change.
String debugId = ddw.getDebugId();

} else {

int separatorPos = name.lastIndexOf("_");
final String pid = name.substring(0, separatorPos);
final Upload uploadComponent = (Upload) idPaintableMap
.get(pid);
if (uploadComponent == null) {
throw new FileUploadException(
"Upload component not found");
}
if (uploadComponent.isReadOnly()) {
throw new FileUploadException(
"Warning: ignored file upload because upload component is set as read-only");
}
synchronized (application) {
handleChangeVariablesError(application,
uploadComponent, e,
new HashMap<String, Object>());
// put upload component into receiving state
uploadComponent.startUpload();
}

// tell UploadProgressListener which component is
// receiving
// file
pl.setUpload(uploadComponent);

try {
uploadComponent.receiveUpload(upstream);
} catch (UploadException e) {
// error happened while receiving file. Handle the
// error in the same manner as it would have
// happened in
// variable change.
synchronized (application) {
handleChangeVariablesError(application,
uploadComponent, e,
new HashMap<String, Object>());
}
}
}

}
}
} catch (final FileUploadException e) {

+ 67
- 17
src/com/vaadin/ui/DragAndDropWrapper.java View File

@@ -3,8 +3,11 @@
*/
package com.vaadin.ui;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.vaadin.event.Transferable;
import com.vaadin.event.TransferableImpl;
@@ -15,10 +18,13 @@ import com.vaadin.event.dd.DropTargetDetails;
import com.vaadin.event.dd.DropTargetDetailsImpl;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.UploadStream;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper;
import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation;
import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File;
import com.vaadin.ui.Upload.Receiver;

@ClientWidget(VDragAndDropWrapper.class)
@@ -27,9 +33,24 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,

public class WrapperTransferable extends TransferableImpl {

private Html5File[] files;

public WrapperTransferable(Component sourceComponent,
Map<String, Object> rawVariables) {
super(sourceComponent, rawVariables);
Integer fc = (Integer) rawVariables.get("filecount");
if (fc != null) {
files = new Html5File[fc];
for (int i = 0; i < fc; i++) {
Html5File file = new Html5File();
String id = (String) rawVariables.get("fi" + i);
file.name = (String) rawVariables.get("fn" + i);
file.size = (Integer) rawVariables.get("fs" + i);
file.type = (String) rawVariables.get("ft" + i);
files[i] = file;
receivers.put(id, file);
}
}
}

/**
@@ -51,21 +72,28 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
}

public Html5File[] getFiles() {
// TODO Auto-generated method stub
return null;
return files;
}

public class Html5File {

public String name;
private String id;
private int size;
private Receiver receiver;
private String type;

public String getFileName() {
// TODO Auto-generated method stub
return null;
return name;
}

public int getFileSize() {
return size;
}

// public int getFileSize() {
// // TODO Auto-generated method stub
// return 0;
// }
public String getType() {
return type;
}

/**
* HTML5 drags are read from client disk with a callback. This and
@@ -77,14 +105,15 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
* implementation writes the file contents as it arrives.
*/
public void receive(Receiver receiver) {
// TODO Auto-generated method stub

this.receiver = receiver;
}

}

}

private Map<String, Html5File> receivers = new HashMap<String, Html5File>();

public class WrapperDropDetails extends DropTargetDetailsImpl {

/**
@@ -195,13 +224,34 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
return dragStartMode;
}

@Override
public void changeVariables(Object source, Map<String, Object> variables) {
super.changeVariables(source, variables);
/**
* This method should only be used by Vaadin terminal implementation. This
* is not end user api.
*
* TODO should fire progress events + end/succes events like upload
*
* @param upstream
* @param fileId
*/
public void receiveFile(UploadStream upstream, String fileId) {
Html5File file = receivers.get(fileId);
if (file != null && file.receiver != null) {
OutputStream receiveUpload = file.receiver.receiveUpload(file
.getFileName(), "TODO");

InputStream stream = upstream.getStream();
byte[] buf = new byte[AbstractApplicationServlet.MAX_BUFFER_SIZE];
int bytesRead;
try {
while ((bytesRead = stream.read(buf)) != -1) {
receiveUpload.write(buf, 0, bytesRead);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Set<String> keySet = variables.keySet();
for (String string : keySet) {
// TODO get files
}

}
}

+ 88
- 6
tests/src/com/vaadin/tests/dd/DDTest6.java View File

@@ -1,5 +1,9 @@
package com.vaadin.tests.dd;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@@ -22,7 +26,9 @@ import com.vaadin.event.dd.acceptCriteria.AcceptCriterion;
import com.vaadin.event.dd.acceptCriteria.IsSameSourceAndTarget;
import com.vaadin.event.dd.acceptCriteria.Not;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.StreamResource;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.terminal.StreamResource.StreamSource;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.tests.components.TestBase;
import com.vaadin.tests.util.TestUtils;
@@ -34,9 +40,12 @@ import com.vaadin.ui.Embedded;
import com.vaadin.ui.Label;
import com.vaadin.ui.SplitPanel;
import com.vaadin.ui.Tree;
import com.vaadin.ui.Window;
import com.vaadin.ui.AbsoluteLayout.ComponentPosition;
import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File;
import com.vaadin.ui.Tree.TreeDragMode;
import com.vaadin.ui.Tree.TreeDropTargetDetails;
import com.vaadin.ui.Upload.Receiver;

public class DDTest6 extends TestBase {

@@ -52,6 +61,8 @@ public class DDTest6 extends TestBase {

private SplitPanel sp;

private BeanItemContainer<File> fs1;

private static int count;

private static DDTest6 instance;
@@ -70,7 +81,7 @@ public class DDTest6 extends TestBase {
tree1 = new Tree("Volume 1");
tree1.setImmediate(true);

BeanItemContainer<File> fs1 = new BeanItemContainer<File>(File.class);
fs1 = new BeanItemContainer<File>(File.class);
tree1.setContainerDataSource(fs1);
for (int i = 0; i < files.length; i++) {
fs1.addBean(files[i]);
@@ -169,11 +180,18 @@ public class DDTest6 extends TestBase {
public static class File {
private Resource icon = DOC;
private String name;
private ByteArrayOutputStream bas;
private String type;

public File(String fileName) {
name = fileName;
}

public File(String fileName, ByteArrayOutputStream bas) {
this(fileName);
this.bas = bas;
}

public void setIcon(Resource icon) {
this.icon = icon;
}
@@ -189,6 +207,28 @@ public class DDTest6 extends TestBase {
public String getName() {
return name;
}

public void setType(String type) {
this.type = type;
}

public String getType() {
return type;
}

public Resource getResource() {
StreamSource streamSource = new StreamSource() {
public InputStream getStream() {
if (bas != null) {
byte[] byteArray = bas.toByteArray();
return new ByteArrayInputStream(byteArray);
}
// TODO Auto-generated method stub
return null;
}
};
return new StreamResource(streamSource, getName(), DDTest6.get());
}
}

public static class Folder extends File {
@@ -202,7 +242,7 @@ public class DDTest6 extends TestBase {

@Override
protected String getDescription() {
return "dd: tree and web desktop tests. TODO add traditional icon area on right side with DragAndDropWrapper and absolutelayouts + more files, auto-opening folders";
return "dd: tree and web desktop tests. FF36 supports draggin files from client side. (try dragging png image + double click) TODO more files, auto-opening folders";
}

@Override
@@ -210,6 +250,16 @@ public class DDTest6 extends TestBase {
return 119;
}

private void openFile(File file) {
// ATM supports only images.

Embedded embedded = new Embedded(file.getName(), file.getResource());
Window w = new Window(file.getName());
w.addComponent(embedded);
getMainWindow().addWindow(w);

}

static class FolderView extends DragAndDropWrapper implements DropHandler {

static final HashMap<Folder, FolderView> views = new HashMap<Folder, FolderView>();
@@ -313,6 +363,35 @@ public class DDTest6 extends TestBase {
File draggedFile = (File) ((DataBoundTransferable) dropEvent
.getTransferable()).getItemId();
DDTest6.get().setParent(draggedFile, folder);
} else {
// expecting this to be an html5 drag
WrapperTransferable tr = (WrapperTransferable) dropEvent
.getTransferable();
Html5File[] files2 = tr.getFiles();
if (files2 != null) {
for (Html5File html5File : files2) {
String fileName = html5File.getFileName();
// int bytes = html5File.getFileSize();
final ByteArrayOutputStream bas = new ByteArrayOutputStream();

Receiver receiver = new Receiver() {
public OutputStream receiveUpload(String filename,
String MIMEType) {
return bas;
}
};

html5File.receive(receiver);

File file = new File(fileName, bas);
file.setType(html5File.getType());
DDTest6.get().fs1.addBean(file);
DDTest6.get().tree1.setChildrenAllowed(file, false);
DDTest6.get().setParent(file, folder);
}

}

}
}

@@ -342,11 +421,15 @@ public class DDTest6 extends TestBase {

l.addListener(new LayoutClickListener() {
public void layoutClick(LayoutClickEvent event) {
if (file instanceof Folder) {
if (event.isDoubleClick()) {
if (event.isDoubleClick()) {
if (file instanceof Folder) {
get().tree1.setValue(file);
} else {
String type = file.getType();
if (type != null && type.equals("image/png")) {
DDTest6.get().openFile(file);
}
}

}

}
@@ -372,7 +455,6 @@ public class DDTest6 extends TestBase {
f = (File) ((DataBoundTransferable) dropEvent
.getTransferable()).getItemId();
}
// TODO accept drags from Tree too

if (f != null) {
get().setParent(f, (Folder) FileIcon.this.file);

Loading…
Cancel
Save