]> source.dussan.org Git - vaadin-framework.git/commitdiff
A proper (FF36 only) file drag support, enhanced test case. Should be forward compatible.
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 12 Mar 2010 14:57:49 +0000 (14:57 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 12 Mar 2010 14:57:49 +0000 (14:57 +0000)
svn changeset:11835/svn branch:6.3

src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
src/com/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent.java
src/com/vaadin/terminal/gwt/client/ui/dd/VHtml5File.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/ui/DragAndDropWrapper.java
tests/src/com/vaadin/tests/dd/DDTest6.java

index 2beef216f29f0ffcc015faa441d39203850db16c..606b382c2679664e69564dae30e49b3492676641 100644 (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;
     }
index 9d5bafc2b77763d01f760943f261fb4da6c06098..47b1ba81ed2cc11be007d92a2219f55f563a878a 100644 (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];
      }-*/;
 
 }
index ead0ee6cebb77c8b73b7e13172061133227c4035..1a1db2910d7700874244bdc5926d169f257c8feb 100644 (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)
index d1b8ec767112f6d256b4997cd70d6d884009aea1..d3243fab73b3c69813037de018a6e0ed5c801b0e 100644 (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;
 
index 481e42c0e9e4c8239cc331a672615b4ef5d8a741..ada3869269ddd9b67aef363c6e1a619c23cf72fb 100644 (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) {
index 3274b60b478709842cdddd6b229fa85a6e781732..a848d7af40fdfaae101ebc8a879c392f2bf91ccd 100644 (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
         }
+
     }
 }
index 6df55917fa84e3e2437ceeedbf1df5b803a759d7..771a3b536f748249bf19287c83c5008115a87286 100644 (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);