Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GMail like file upload progress bar with GWT?

Tags:

gwt

All Gmail users should have already noticed that file upload progress bar has been updated recently.

I'm wondering such effect is possible to implement with GWT. I'm fairly new with GWT, so if any GWT source code that can help me test out the function would be very helpful.

Update
I ended up going with SWFUpload. However, other suggestions under this question are all valid. Just try different options and choose the one you like!

like image 707
codingbear Avatar asked Mar 09 '09 03:03

codingbear


4 Answers

Take a look to this library: http://code.google.com/p/gwtupload/. It is really easy to to use and works fine in all browsers and OS I've checked. It uses ajax requests to calculate progress. BTW Swfupload doesn't do well in linux and Mac.

like image 78
mcdodot Avatar answered Nov 20 '22 03:11

mcdodot


I've used this tool before:

http://code.google.com/p/gwt-fileapi/

Unlike the other suggestions here, not only does it give the proper API to show upload progress, it also gives the ability to do batch uploads by selecting multiple files, and it also gives drag and drop support. It also has a pre HTML5 fallback mechanism.

I've had had great luck with it gwt-fileap. Recently it broke in Firefox 7 and 8 and I had to apply this patch to it - but otherwise it works really great:

@@ -57,26 +57,33 @@

     /**
      * gets the filename
-     * 
+     *
      * @return the filename
      */
     public final native String getFileName() /*-{
-        return this.fileName;
+        if(this.name)
+                    return this.name;
+         else
+                    return this.fileName;
+
     }-*/;

     /**
      * gets the file size in bytes
-     * 
+     *
      * @return the file size in bytes
      */
     public final native int getFileSize() /*-{
-        return this.fileSize;
+        if(this.size)
+                    return this.size;
+         else
+                    return this.fileSize;
     }-*/;

     /**
      * gets the MIME type of the file, may be null if the browser cannot detect
      * the type

I also had to add the following lines to http://code.google.com/p/gwt-fileapi/source/browse/trunk/gwt-fileapi/src/com/gwtpro/html5/fileapi/Html5FileApi.gwt.xml - these lines describe how the fallback mechanism works. You can do something similar if you want your code to fall back on the SWFUploader implementation shown below in case HTML5 is missing.

    <define-property name="fileapi.support" values="yes,no" />

    <property-provider name="fileapi.support"><![CDATA[
                   var input=document.createElement('input');
                    input.setAttribute('type','file');
                    return input.files==null?'no':'yes';
    ]]></property-provider>


    <replace-with
            class="com.gwtpro.html5.fileapi.client.ui.FileInput.FileInputImplHtml5">
            <when-type-is
                    class="com.gwtpro.html5.fileapi.client.ui.FileInput.FileInputImpl" />
            <when-property-is name="fileapi.support" value="yes" />
            <any>
                    <when-property-is name="user.agent" value="ie8" />
                    <when-property-is name="user.agent" value="safari" />
                    <when-property-is name="user.agent" value="gecko1_8" />
                    <when-property-is name="user.agent" value="opera" />
                    <when-property-is name="user.agent" value="chrome" /> 
            </any>
    </replace-with>

This is how I use it in my application:

This is the interface that describes the abstraction:

public interface FileUpload {
    public void uploadFiles();
    public Widget getWidget();
    public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler);
    public void setDisabled(boolean b);
    public  void readyToPaint();
    public void reset();

}

The following is the gwt-fileapi implementation of the interface:

package com.hierarchycm.gxt.client.fileUpload;

import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.gwtpro.html5.fileapi.client.FileApiSupport;
import com.gwtpro.html5.fileapi.client.drop.DropHandler;
import com.gwtpro.html5.fileapi.client.file.File;
import com.gwtpro.html5.fileapi.client.file.FileEvent;
import com.gwtpro.html5.fileapi.client.file.FileEvent.FileEventHandler;
import com.gwtpro.html5.fileapi.client.ui.FileInput;
import com.gwtpro.html5.fileapi.client.upload.UploadRequest;
import com.gwtpro.html5.fileapi.client.upload.UploadRequestBuilder;
import com.gwtpro.html5.fileapi.client.upload.UploadRequestCallback;

public class FileUploadHtmlImpl extends FileInput implements FileUpload {

    private Grid uploadTable;   
    int currentFile =0;
    String url;
    File[] files;   
    UploadRequestBuilder fileUploader;
    Uploader uploader;

    public FileUploadHtmlImpl() {

    }

    FileUploadHtmlImpl(Grid updateTable, Uploader uploader, String url) {
        this(updateTable, uploader, url, true);
    }

    FileUploadHtmlImpl(Grid updateTable, Uploader uploader, String url, boolean createDropHandler) {
        initialize(updateTable, uploader, url, createDropHandler);
        //this.setCallback(getMyCallback());
    }

    public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler){
        this.url = url;
        this.uploadTable = updateTable;
        this.uploader = uploader;
        this.setAllowMultipleFiles(true);
        this.addChangeHandler(new ChangeHandler() {
                @Override
                public void onChange(ChangeEvent event) {
                    addFiles(FileUploadHtmlImpl.this.getFiles());   
                    uploadFiles();
                }
          });

        if (createDropHandler) {
            createDropHandler();
        }
    }

     private File[] jsArrToArr (JsArray<File> ipFiles) { 

         File [] result = new File [ipFiles.length()];       
         for (int i = 0; i < ipFiles.length(); ++i) {
             result[i] = ipFiles.get(i);
         }
         return result;
     }

    private UploadRequestCallback getMyCallback() {
        return new UploadRequestCallback() {

            @Override
            public void onError(UploadRequest request, Throwable exception) {
                uploadTable.setText(currentFile + 1, 2, "failed: " + exception.getMessage());
                uploadNextFile(currentFile + 1);
            }

            @Override
            public void onResponseReceived(UploadRequest request, Response response) {
                uploadTable.setText(currentFile + 1, 2, "success: " + response.getText());
                uploadNextFile(currentFile + 1);

                //If we just finished uploading  do your thing
                if (currentFile == files.length) {
                    setDisabled(false);
                    uploader.uploadDoneEventHandler();
                }
            }

            @Override
            public void onUploadProgress(UploadRequest request, int bytesUploaded) {
                uploadTable.setText(currentFile + 1, 2, bytesUploaded + "");
            }
        };
    }

    public void createDropHandler() {
          RootPanel rootPanel = RootPanel.get();
          DropHandler dropHandler = new DropHandler(rootPanel);
            this.fileUploader = new UploadRequestBuilder(url);
            this.fileUploader.setCallback(getMyCallback());
            dropHandler.addFileEventHandler(new FileEventHandler() {

                @Override
                public void onFiles(FileEvent event) {
                    addFiles(jsArrToArr(event.getFiles()));
                    uploadFiles();
                }
            });
    }

     private void addFiles (File[] ipFiles) {
            files = ipFiles;
            uploadTable.clear();
            uploadTable.resize(files.length + 1, 3);
            uploadTable.setText(0, 0, "File name");
            uploadTable.setText(0, 1, "File size");
            uploadTable.setText(0, 2, "Progress");
            for (int i = 0; i < files.length; ++i) {                
                uploadTable.setText(i + 1, 0, files[i].getFileName());                          
                uploadTable.setText(i + 1, 1, files[i].getFileSize() + "");
                uploadTable.setText(i + 1, 2, "");
            }
    }

    public void uploadNextFile(int index) {
            for (String paramName : uploader.getPostParams().keySet()) {
                fileUploader.setHeader(paramName, uploader.getPostParams().get(paramName));                                     
            }

            currentFile = index;
            this.setDisabled(true);
            if (index < this.files.length) {
                try {
                    this.fileUploader.setHeader("itemName", files[currentFile].getFileName());
                    this.fileUploader.sendFile(files[currentFile]);
                } catch (RequestException e) {
                    this.uploadTable.setText(index + 1, 2, "failed: " + e.getMessage());
                    uploadNextFile(index + 1);
                }
            }


     }

    public void uploadFiles() {
        uploadNextFile(0);
    }

    @Override
    public Widget getWidget() {
        return this;
    }

    @Override
    public void readyToPaint() {
        //no need to do anything - already painted for non swf
    }

    @Override
    public void reset() {
        // TODO Auto-generated method stub

    }

    private void showCapabilities() {
        RootPanel
                .get("status")
                .getElement()
                .setInnerHTML(
                        "Drag and Drop Support: "
                                + (FileApiSupport.isDragDropSupported() ? "Yes"
                                        : "No")
                                + "<br/>HTTPXmlRequest Level 2: "
                                + (FileApiSupport.isHttpXmlRequestLevel2() ? "Yes"
                                        : "No")
                                + "<br/>File input supports multiple files: "
                                + (FileApiSupport
                                        .isMultipleFileInputSupported() ? "Yes"
                                        : "No")+"<br/><br/>");
    }

}

This is the SWFUpload http://code.google.com/p/swfupload-gwt/ implementation of the same interface:

package com.hierarchycm.gxt.client.fileUpload;

import com.extjs.gxt.ui.client.widget.Html;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Widget;

public class FileUploadSwfImpl extends Html implements FileUpload {

    SwfUploadUtil swfUploadUtil = null;
    private Uploader uploader;
    private String url;
    private boolean createDropHandler;
    private Grid updateTable;



    static int uploadId = 0; 
    static String divTagId;

    public FileUploadSwfImpl() {
        divTagId = "swfupload" + uploadId++;
        String divTag = "<div id=\"" + divTagId + "\"></div";
        this.setHtml(divTag);
    }

    @Override
    public void uploadFiles() {
        swfUploadUtil.startUpload();        
    }

    @Override
    public Widget getWidget() {     
        return this;
    }

     public  void readyToPaint() {      

         swfUploadUtil =  new SwfUploadUtil(uploader, updateTable, divTagId, url);       
     }

    @Override
    public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler) {

        this.uploader = uploader;
        this.url = url;
        this.createDropHandler = createDropHandler;
        this.updateTable = updateTable;

    }

    @Override
    public void setDisabled(boolean b) {

        swfUploadUtil.setDisabled(b);
        this.disabled = true;

    }

    @Override
    public void reset() {
        swfUploadUtil.reset();

    }
}

And this is the utility the FileUploadSwfImpl depends on:

package com.hierarchycm.gxt.client.fileUpload;

import java.util.HashMap;

import org.swfupload.client.File;
import org.swfupload.client.SWFUpload;
import org.swfupload.client.UploadBuilder;
import org.swfupload.client.SWFUpload.ButtonAction;
import org.swfupload.client.SWFUpload.ButtonCursor;
import org.swfupload.client.event.DialogStartHandler;
import org.swfupload.client.event.FileDialogCompleteHandler;
import org.swfupload.client.event.FileQueuedHandler;
import org.swfupload.client.event.UploadCompleteHandler;
import org.swfupload.client.event.UploadErrorHandler;
import org.swfupload.client.event.UploadProgressHandler;
import org.swfupload.client.event.UploadSuccessHandler;
import org.swfupload.client.event.FileDialogCompleteHandler.FileDialogCompleteEvent;
import org.swfupload.client.event.FileQueuedHandler.FileQueuedEvent;
import org.swfupload.client.event.UploadCompleteHandler.UploadCompleteEvent;
import org.swfupload.client.event.UploadErrorHandler.UploadErrorEvent;
import org.swfupload.client.event.UploadProgressHandler.UploadProgressEvent;
import org.swfupload.client.event.UploadSuccessHandler.UploadSuccessEvent;

import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Grid;

public class SwfUploadUtil {

    HashMap<String, Integer> filenameRowHm = new HashMap<String, Integer>(); 

    private boolean resetIssued;

    SWFUpload swfUpload = null;
    private HashMap <String, File> files = new HashMap<String, File>();     
    int tableRow = 5;   
    Uploader uploader = null;
    private Grid updateTable;
    private String divName;
    private String url;

    synchronized private void removeFile(String id) {
        files.remove(id);
    }       

    public SwfUploadUtil(Uploader uploader, Grid updateTable,  String divName, String url){
        reset();
        this.uploader = uploader;
        this.updateTable = updateTable;
        this.divName = divName;
        this.url = url;

        this.swfUpload = loadSWFUpload();
        updateTable.resize(5, 5);
        updateTable.setText(2, 0, "Upload URL:" );
        updateTable.setText(2, 1, url );        
        updateTable.setText(4, 0, "File Name" );
        updateTable.setText(4, 1, "Bytes In");
        updateTable.setText(4, 2, "Status");
        updateTable.setText(4, 3, "File Size" );
        updateTable.setText(4, 4, "Server response" );

    }


    public SWFUpload loadSWFUpload() {

        this.updateTable = updateTable;

        if (swfUpload == null) {        
            final UploadBuilder builder1 = new UploadBuilder();
            builder1.setHTTPSuccessCodes(200, 201);
            builder1.setFileTypes("*.webm;*.asf;*.wma;*.wmv;*.avi;*.flv;*.swf;*.mpg;*.mpeg;*.mp4;*.mov;*.m4v;*.aac;*.mp3;*.wav;*.png;*.jpg;*.jpeg;*.gif");
            builder1.setFileTypesDescription("Images, Video & Sound");

            builder1.setButtonPlaceholderID(divName);
            builder1.setButtonImageURL("./images/XPButtonUploadText_61x22.png");
            builder1.setButtonCursor(ButtonCursor.HAND);
            builder1.setButtonWidth(61);
            builder1.setButtonHeight(22);
            builder1.setButtonAction(ButtonAction.SELECT_FILES);

            builder1.setUploadProgressHandler(new UploadProgressHandler() {

                public void onUploadProgress(UploadProgressEvent e) {

                    File f = e.getFile();                   
                    updateTable.setText(getFilenameRow(f), 2, String.valueOf(e.getBytesComplete()));

                }
            });

            builder1.setUploadSuccessHandler(new UploadSuccessHandler() {
                public void onUploadSuccess(UploadSuccessEvent e) {
                    File f = e.getFile();
                    updateTable.setText(getFilenameRow(f), 4, e.getServerData());
                }
            }); 

            builder1.setUploadErrorHandler(new UploadErrorHandler() {
                public void onUploadError(UploadErrorEvent e) {
                    File ff = e.getFile(); 
                    String message = e.getMessage(); 
                    if (message == null || message.trim().length() == 0) {
                        message = "upload failed"; 
                    }               
                    updateTable.setText(getFilenameRow(ff), 2, String.valueOf(message));

                    removeFile(ff.getId()); 
                    if (files.values().size() > 0) {
                        ff = files.values().iterator().next(); 
                        updateTable.setText(getFilenameRow(ff), 2, "Started");
                        swfUpload.startUpload(ff.getId());                      
                    }
                }
            }); 

            builder1.setUploadURL(url); 

            builder1.setDialogStartHandler(new DialogStartHandler() {
                @Override
                public void onDialogStart() {
                    if(resetIssued == true) { 
                        filenameRowHm.clear();
                        resetIssued = false;
                    }               
                }                   
            }
            );

            builder1.setUploadCompleteHandler(new UploadCompleteHandler() {
                public void onUploadComplete(UploadCompleteEvent e) {
                    File f = e.getFile(); 

                    updateTable.setText(getFilenameRow(f), 2, "Done");

                    removeFile(f.getId()); 
                    if (files.values().size() > 0) {
                        File ff = files.values().iterator().next(); 

                        updateTable.setText(getFilenameRow(ff), 2, "Started");
                        swfUpload.startUpload(ff.getId()); 
                    } else {                    
                        uploader.uploadDoneEventHandler();
                    }
                }
            });

            builder1.setFileQueuedHandler(new FileQueuedHandler() {
                public void onFileQueued(FileQueuedEvent event) {

                    File f = event.getFile();                   
                    updateTable.setText(getFilenameRow(f), 2, "Queued");                    
                    files.put(f.getId(), f); 
                }
            });

            builder1.setFileDialogCompleteHandler(new FileDialogCompleteHandler() {
                public void onFileDialogComplete(FileDialogCompleteEvent e) {                                                   



                    updateTable.setText(2, 0, "Number of files");
                    updateTable.setText(2, 1, String.valueOf(files.values().size()));

                    for(File f : files.values()) {
                        getFilenameRow(f);
                    }

                    if (files.values().size() > 0) {

                        for (String paramName : uploader.getPostParams().keySet()) {
                            swfUpload.addPostParam(paramName,uploader.getPostParams().get(paramName));                          
                        }   
                    }
                }
            });
            swfUpload = builder1.build();

        }

        return swfUpload;

    }

    public int getFilenameRow (File f) {
        Integer filenamerow = filenameRowHm.get(f.getId());

        if (filenamerow == null) {
            updateTable.resize(tableRow+1, 5);
            filenamerow = new Integer(tableRow++);
            updateTable.setText(filenamerow.intValue(), 0, f.getName());
            updateTable.setText(filenamerow.intValue(), 3, String.valueOf(f.getSize()));
            //updateTable.setText(filenamerow.intValue(), 3, String.valueOf(f));
            filenameRowHm.put(f.getId(), filenamerow);
        }

        return filenamerow.intValue();
    }

    public void startUpload() {
        uploader.uploadStartedEventHandler();
        swfUpload.startUpload();
    }

    public void setDisabled(boolean disabled) {
        swfUpload.setButtonDisabled(disabled);


    }

    public void reset() {
        // TODO Auto-generated method stub
        resetIssued = true;
    }
}
like image 24
Ted Gulesserian Avatar answered Nov 20 '22 04:11

Ted Gulesserian


Use SWFUpload via swfupload-gwt

The main advantage over the other methods is this does not require any special server code. You could even upload to another domain (if there is a crossdomain.xml which allows it).

like image 4
Mark Renouf Avatar answered Nov 20 '22 04:11

Mark Renouf


Check out GWTC Upload, which has an implementation of exactly what you're looking for.

like image 3
John Feminella Avatar answered Nov 20 '22 03:11

John Feminella