Browse Source

multiple upload related enhancements

 - implemented interruption of upload #963
 - added generics
 - changed deprecated gwt methods to 1.6 handlers
 - immediate mode now works (also styled as "one button upload")

svn changeset:8442/svn branch:6.0
tags/6.7.0.beta1
Matti Tahvonen 15 years ago
parent
commit
b84bf1a1bd

+ 24
- 0
WebContent/VAADIN/themes/base/styles.css View File

@@ -1436,6 +1436,30 @@ div.v-tree-node-leaf {
clear: left;
}

/* ./WebContent/VAADIN/themes/base/upload/upload.css */
.v-upload-immediate {
position: relative;
width: 10em;
margin:0;
}

.v-upload-immediate input {
opacity: 0;
filter: alpha(opacity=0);
z-index:2;
position: absolute;
right:0;
height:21px;
text-align: right;
}
.v-upload-immediate button {
position:relative;
left:0;
top:0;
width:100%;
}

/* ./WebContent/VAADIN/themes/base/window/window.css */
.v-window {
background: #fff;

+ 22
- 0
WebContent/VAADIN/themes/base/upload/upload.css View File

@@ -0,0 +1,22 @@
.v-upload-immediate {
position: relative;
width: 10em;
margin:0;
}

.v-upload-immediate input {
opacity: 0;
filter: alpha(opacity=0);
z-index:2;
position: absolute;
right:0;
height:21px;
text-align: right;
}
.v-upload-immediate button {
position:relative;
left:0;
top:0;
width:100%;
}

+ 24
- 0
WebContent/VAADIN/themes/reindeer/styles.css View File

@@ -1436,6 +1436,30 @@ div.v-tree-node-leaf {
clear: left;
}

/* ./WebContent/VAADIN/themes/base/upload/upload.css */
.v-upload-immediate {
position: relative;
width: 10em;
margin:0;
}

.v-upload-immediate input {
opacity: 0;
filter: alpha(opacity=0);
z-index:2;
position: absolute;
right:0;
height:21px;
text-align: right;
}
.v-upload-immediate button {
position:relative;
left:0;
top:0;
width:100%;
}

/* ./WebContent/VAADIN/themes/base/window/window.css */
.v-window {
background: #fff;

+ 24
- 0
WebContent/VAADIN/themes/runo/styles.css View File

@@ -1436,6 +1436,30 @@ div.v-tree-node-leaf {
clear: left;
}

/* ./WebContent/VAADIN/themes/base/upload/upload.css */
.v-upload-immediate {
position: relative;
width: 10em;
margin:0;
}

.v-upload-immediate input {
opacity: 0;
filter: alpha(opacity=0);
z-index:2;
position: absolute;
right:0;
height:21px;
text-align: right;
}
.v-upload-immediate button {
position:relative;
left:0;
top:0;
width:100%;
}

/* ./WebContent/VAADIN/themes/base/window/window.css */
.v-window {
background: #fff;

+ 116
- 39
src/com/vaadin/terminal/gwt/client/ui/VUpload.java View File

@@ -4,30 +4,53 @@

package com.vaadin.terminal.gwt.client.ui;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormHandler;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormSubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormSubmitEvent;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;

public class VUpload extends FormPanel implements Paintable, ClickListener,
FormHandler {
public class VUpload extends FormPanel implements Paintable,
SubmitCompleteHandler, SubmitHandler {

private final class MyFileUpload extends FileUpload {
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
if (event.getTypeInt() == Event.ONCHANGE) {
if (immediate && fu.getFilename() != null
&& !"".equals(fu.getFilename())) {
submit();
}
} else if (event.getTypeInt() == Event.ONFOCUS) {
// IE and user has clicked on hidden textarea part of upload
// field. Manually open file selector, other browsers do it by
// default.
fireNativeClick(fu.getElement());
// also remove focus to enable hack if user presses cancel
// button
fireNativeBlur(fu.getElement());
}
}
}

public static final String CLASSNAME = "v-upload";

/**
* FileUpload component that opens native OS dialog to select file.
*/
FileUpload fu = new FileUpload();
FileUpload fu = new MyFileUpload();

Panel panel = new FlowPanel();

@@ -56,18 +79,34 @@ public class VUpload extends FormPanel implements Paintable, ClickListener,

private boolean enabled = true;

private boolean immediate;

private Hidden maxfilesize = new Hidden();

public VUpload() {
super();
setEncoding(FormPanel.ENCODING_MULTIPART);
setMethod(FormPanel.METHOD_POST);

setWidget(panel);
panel.add(maxfilesize);
panel.add(fu);
submitButton = new Button();
submitButton.addClickListener(this);
submitButton.setStyleName("v-button");
submitButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
if (immediate) {
// fire click on upload (eg. focused button and hit space)
fireNativeClick(fu.getElement());
} else {
submit();
}
}
});
panel.add(submitButton);

addFormHandler(this);
addSubmitCompleteHandler(this);
addSubmitHandler(this);

setStyleName(CLASSNAME);
}
@@ -76,10 +115,12 @@ public class VUpload extends FormPanel implements Paintable, ClickListener,
if (client.updateComponent(this, uidl, true)) {
return;
}
setImmediate(uidl.getBooleanAttribute("immediate"));
this.client = client;
paintableId = uidl.getId();
setAction(client.getAppUri());
submitButton.setText(uidl.getStringAttribute("buttoncaption"));
submitButton.setHTML("<span class=\"v-button-caption\">"
+ uidl.getStringAttribute("buttoncaption") + "</span>");
fu.setName(paintableId + "_file");

if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) {
@@ -87,16 +128,76 @@ public class VUpload extends FormPanel implements Paintable, ClickListener,
} else if (uidl.getBooleanAttribute("state")) {
enableUploaod();
}
}

private void setImmediate(boolean booleanAttribute) {
if (immediate != booleanAttribute) {
immediate = booleanAttribute;
if (immediate) {
fu.sinkEvents(Event.ONCHANGE);
fu.sinkEvents(Event.ONFOCUS);
}
}
setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
}

public void onClick(Widget sender) {
submit();
private static native void fireNativeClick(Element element)
/*-{
element.click();
}-*/;

private static native void fireNativeBlur(Element element)
/*-{
element.blur();
}-*/;

protected void disableUpload() {
submitButton.setEnabled(false);
fu.setVisible(false);
enabled = false;
}

public void onSubmit(FormSubmitEvent event) {
protected void enableUploaod() {
submitButton.setEnabled(true);
fu.setVisible(true);
enabled = true;
}

/**
* Re-creates file input field and populates panel. This is needed as we
* want to clear existing values from our current file input field.
*/
private void rebuildPanel() {
panel.remove(submitButton);
panel.remove(fu);
fu = new MyFileUpload();
fu.setName(paintableId + "_file");
fu.setVisible(enabled);
panel.add(fu);
panel.add(submitButton);
if (immediate) {
fu.sinkEvents(Event.ONCHANGE);
}
}

public void onSubmitComplete(SubmitCompleteEvent event) {
if (client != null) {
if (t != null) {
t.cancel();
}
ApplicationConnection.getConsole().log("Submit complete");
client.sendPendingVariableChanges();
}

rebuildPanel();

submitted = false;
enableUploaod();
}

public void onSubmit(SubmitEvent event) {
if (fu.getFilename().length() == 0 || submitted || !enabled) {
event.setCancelled(true);
event.cancel();
ApplicationConnection
.getConsole()
.log(
@@ -125,28 +226,4 @@ public class VUpload extends FormPanel implements Paintable, ClickListener,
t.schedule(800);
}

protected void disableUpload() {
submitButton.setEnabled(false);
fu.setVisible(false);
enabled = false;
}

protected void enableUploaod() {
submitButton.setEnabled(true);
fu.setVisible(true);
enabled = true;
}

public void onSubmitComplete(FormSubmitCompleteEvent event) {
if (client != null) {
if (t != null) {
t.cancel();
}
ApplicationConnection.getConsole().log("Submit complete");
client.sendPendingVariableChanges();
}
submitted = false;
enableUploaod();
}

}

+ 288
- 0
src/com/vaadin/tests/TestForStyledUpload.java View File

@@ -0,0 +1,288 @@
/*
@ITMillApache2LicenseForJavaFiles@
*/

package com.vaadin.tests;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;

import com.vaadin.Application;
import com.vaadin.terminal.StreamResource;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Link;
import com.vaadin.ui.Panel;
import com.vaadin.ui.ProgressIndicator;
import com.vaadin.ui.Upload;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Upload.FailedEvent;
import com.vaadin.ui.Upload.FailedListener;
import com.vaadin.ui.Upload.FinishedEvent;
import com.vaadin.ui.Upload.FinishedListener;
import com.vaadin.ui.Upload.StartedEvent;
import com.vaadin.ui.Upload.StartedListener;
import com.vaadin.ui.Upload.SucceededEvent;
import com.vaadin.ui.Upload.SucceededListener;

public class TestForStyledUpload extends Application implements
Upload.FinishedListener, FailedListener, SucceededListener,
StartedListener {

Layout main = new VerticalLayout();

TmpFileBuffer buffer = new TmpFileBuffer();

Panel status = new Panel("Uploaded file:");

private final Upload up;

private final Label l;

private final Label transferred = new Label("");

private final ProgressIndicator pi = new ProgressIndicator();

private final Label memoryStatus;

public TestForStyledUpload() {
main
.addComponent(new Label(
"Clicking on button b updates information about upload components status or same with garbage collector."));

up = new Upload(null, buffer);
up.setButtonCaption("Select file");
up.setImmediate(true);
up.addListener((FinishedListener) this);
up.addListener((FailedListener) this);
up.addListener((SucceededListener) this);
up.addListener((StartedListener) this);

up.addListener(new Upload.ProgressListener() {

public void updateProgress(long readBytes, long contentLenght) {
pi.setValue(new Float(readBytes / (float) contentLenght));

refreshMemUsage();

transferred.setValue("Transferred " + readBytes + " of "
+ contentLenght);
}

});

final Button b = new Button("Update status", this, "readState");

final Button c = new Button("Update status with gc", this, "gc");

main.addComponent(up);
l = new Label("Idle");
main.addComponent(l);

pi.setVisible(false);
pi.setPollingInterval(300);
main.addComponent(pi);
main.addComponent(transferred);

memoryStatus = new Label();
main.addComponent(memoryStatus);

status.setVisible(false);
main.addComponent(status);

Button cancel = new Button("Cancel current upload");
cancel.addListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
buffer.cancel();
}
});

main.addComponent(cancel);

final Button restart = new Button("Restart demo application");
restart.addListener(new Button.ClickListener() {

public void buttonClick(ClickEvent event) {
TestForStyledUpload.this.close();
}
});
main.addComponent(restart);
main.addComponent(b);
main.addComponent(c);

}

public void gc() {
Runtime.getRuntime().gc();
readState();
}

public void readState() {
final StringBuffer sb = new StringBuffer();

if (up.isUploading()) {
sb.append("Uploading...");
sb.append(up.getBytesRead());
sb.append("/");
sb.append(up.getUploadSize());
sb.append(" ");
sb.append(Math.round(100 * up.getBytesRead()
/ (double) up.getUploadSize()));
sb.append("%");
} else {
sb.append("Idle");
}
l.setValue(sb.toString());
refreshMemUsage();
}

public void uploadFinished(FinishedEvent event) {
status.removeAllComponents();
final InputStream stream = buffer.getStream();
if (stream == null) {
status.addComponent(new Label(
"Upload finished, but output buffer is null!!"));
} else {
status
.addComponent(new Label("<b>Name:</b> "
+ event.getFilename(), Label.CONTENT_XHTML));
status.addComponent(new Label("<b>Mimetype:</b> "
+ event.getMIMEType(), Label.CONTENT_XHTML));
status.addComponent(new Label("<b>Size:</b> " + event.getLength()
+ " bytes.", Label.CONTENT_XHTML));

status.addComponent(new Link("Download " + buffer.getFileName(),
new StreamResource(buffer, buffer.getFileName(), this)));

status.setVisible(true);
}
}

public interface Buffer extends StreamResource.StreamSource,
Upload.Receiver {

String getFileName();
}

public class TmpFileBuffer implements Buffer {
String mimeType;

String fileName;

private File file;

private FileInputStream stream;

public TmpFileBuffer() {
final String tempFileName = "upload_tmpfile_"
+ System.currentTimeMillis();
try {
file = File.createTempFile(tempFileName, null);
} catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

public void cancel() {
up.interruptUpload();
}

public InputStream getStream() {
if (file == null) {
return null;
}
try {
stream = new FileInputStream(file);
return stream;
} catch (final FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

/**
* @see com.vaadin.ui.Upload.Receiver#receiveUpload(String, String)
*/
public OutputStream receiveUpload(String filename, String MIMEType) {
fileName = filename;
mimeType = MIMEType;
try {
return new FileOutputStream(file);
} catch (final FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

/**
* Returns the fileName.
*
* @return String
*/
public String getFileName() {
return fileName;
}

/**
* Returns the mimeType.
*
* @return String
*/
public String getMimeType() {
return mimeType;
}

}

public void uploadFailed(FailedEvent event) {
pi.setVisible(false);
l.setValue("Upload was interrupted");
}

public void uploadSucceeded(SucceededEvent event) {
pi.setVisible(false);
l.setValue("Finished upload, idle");
System.out.println(event);
}

private void refreshMemUsage() {
// memoryStatus.setValue("Not available in Java 1.4");
StringBuffer mem = new StringBuffer();
MemoryMXBean mmBean = ManagementFactory.getMemoryMXBean();
mem.append("Heap (M):");
mem.append(mmBean.getHeapMemoryUsage().getUsed() / 1048576);
mem.append(" | Non-Heap (M):");
mem.append(mmBean.getNonHeapMemoryUsage().getUsed() / 1048576);
memoryStatus.setValue(mem.toString());

}

public void uploadStarted(StartedEvent event) {
pi.setVisible(true);
l.setValue("Started uploading file " + event.getFilename());
}

@Override
public void init() {
Window w = new Window();
w.setTheme("runo");
w.addComponent(main);
setMainWindow(w);

}

}

+ 48
- 8
src/com/vaadin/ui/Upload.java View File

@@ -4,6 +4,7 @@

package com.vaadin.ui;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
@@ -20,13 +21,16 @@ import com.vaadin.terminal.UploadStream;
/**
* Component for uploading files from client to server.
*
* <p>
* The visible component consists of a file name input box and a browse button
* and an upload submit button to start uploading.
*
* <p>
* The Upload component needs a java.io.OutputStream to write the uploaded data.
* You need to implement the Upload.Receiver interface and return the output
* stream in the receiveUpload() method.
*
* <p>
* You can get an event regarding starting (StartedEvent), progress
* (ProgressEvent), and finishing (FinishedEvent) of upload by implementing
* StartedListener, ProgressListener, and FinishedListener, respectively. The
@@ -34,10 +38,16 @@ import com.vaadin.terminal.UploadStream;
* to separate between these two cases, you can use SucceededListener
* (SucceededEvenet) and FailedListener (FailedEvent).
*
* <p>
* The upload component does not itself show upload progress, but you can use
* the ProgressIndicator for providing progress feedback by implementing
* ProgressListener and updating the indicator in updateProgress().
*
* <p>
* Setting upload component immediate initiates the upload as soon as a file is
* selected, instead of the common pattern of file selection field and upload
* button.
*
* @author IT Mill Ltd.
* @version
* @VERSION@
@@ -81,7 +91,9 @@ public class Upload extends AbstractComponent implements Component.Focusable {
* upload
*/
private ProgressListener progressListener;
private LinkedHashSet progressListeners;
private LinkedHashSet<ProgressListener> progressListeners;

private boolean interrupted = false;

/* TODO: Add a default constructor, receive to temp file. */

@@ -169,6 +181,9 @@ public class Upload extends AbstractComponent implements Component.Focusable {
fireUpdateProgress(totalBytes, contentLength);
}
}
if (interrupted) {
throw new Exception("Upload interrupted by other thread");
}
}

// upload successful
@@ -182,8 +197,15 @@ public class Upload extends AbstractComponent implements Component.Focusable {
} catch (final Exception e) {
synchronized (application) {
// Download interrupted
try {
// still try to close output stream
out.close();
} catch (IOException e1) {
// NOP
}
fireUploadInterrupted(filename, type, totalBytes, e);
endUpload();
interrupted = false;
}
}
}
@@ -697,7 +719,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
*/
public void addListener(ProgressListener listener) {
if (progressListeners == null) {
progressListeners = new LinkedHashSet();
progressListeners = new LinkedHashSet<ProgressListener>();
}
progressListeners.add(listener);
}
@@ -792,8 +814,9 @@ public class Upload extends AbstractComponent implements Component.Focusable {
// this is implemented differently than other listeners to maintain
// backwards compatibility
if (progressListeners != null) {
for (Iterator it = progressListeners.iterator(); it.hasNext();) {
ProgressListener l = (ProgressListener) it.next();
for (Iterator<ProgressListener> it = progressListeners.iterator(); it
.hasNext();) {
ProgressListener l = it.next();
l.updateProgress(totalBytes, contentLength);
}
}
@@ -880,13 +903,24 @@ public class Upload extends AbstractComponent implements Component.Focusable {
isUploading = true;
}

/**
* Interrupts the upload currently being received. The interruption will be
* done by the receiving tread so this method will return immediately and
* the actual interrupt will happen a bit later.
*/
public void interruptUpload() {
if (isUploading) {
interrupted = true;
}
}

/**
* Go into state where new uploading can begin.
*
* Warning: this is an internal method used by the framework and should not
* be used by user of the Upload component.
*/
public void endUpload() {
private void endUpload() {
isUploading = false;
contentLength = -1;
}
@@ -960,11 +994,17 @@ public class Upload extends AbstractComponent implements Component.Focusable {
}

/**
* File uploads usually have button that starts actual upload progress. This
* method is used to set text in that button.
* In addition to the actual file chooser, upload components have button
* that starts actual upload progress. This method is used to set text in
* that button.
*
* <p>
* <strong>Note</strong> the string given is set as is to the button. HTML
* formatting is not stripped. Be sure to properly validate your value
* according to your needs.
*
* @param buttonCaption
* text for uploads button.
* text for upload components button.
*/
public void setButtonCaption(String buttonCaption) {
this.buttonCaption = buttonCaption;

Loading…
Cancel
Save