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!
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.
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;
}
}
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).
Check out GWTC Upload, which has an implementation of exactly what you're looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With