Friday 7 December 2007
Simulate ajax file upload with wicket
Par Vincent DEMAY, Friday 7 December 2007 :: Wicket
UploadPanel.java
package net.demay.fr.component.ajax.upload;
import org.apache.wicket.Page;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.markup.html.link.IPageLink;
import org.apache.wicket.markup.html.link.InlineFrame;
import org.apache.wicket.markup.html.panel.Panel;
/**
* A panel allowing to upload file in an iframe. This panel can be used to
* upload file in a "Ajax way" : page does not need to be reloaded. Only the iframe is reloaded
*
* @author Vincent Demay
*
*/
@SuppressWarnings("serial")
public abstract class UploadPanel extends Panel {
private InlineFrame uploadIFrame = null;
public UploadPanel(String id) {
super(id);
addOnUploadedCallback();
setOutputMarkupId(true);
}
/**
* Called when the upload load is uploaded and ready to be used
* Return the url of the new uploaded resource
* @param upload {@link FileUpload}
*/
public abstract String onFileUploaded(FileUpload upload);
/**
* Called once the upload is finished and the traitment of the
* {@link FileUpload} has been done in {@link UploadPanel#onFileUploaded}
* @param target an {@link AjaxRequestTarget}
* @param fileName name of the file on the client side
* @param newFileUrl Url of the uploaded file
*/
public abstract void onUploadFinished(AjaxRequestTarget target, String filename, String newFileUrl);
protected void onBeforeRender() {
super.onBeforeRender();
if (uploadIFrame == null) {
// the iframe should be attached to a page to be able to get its pagemap,
// that's why i'm adding it in onBeforRender
addUploadIFrame();
}
}
/**
* Create the iframe containing the upload widget
*
*/
private void addUploadIFrame() {
IPageLink iFrameLink = new IPageLink() {
public Page getPage() {
return new UploadIFrame() {
@Override
protected String getOnUploadedCallback() {
return "onUpload_" + UploadPanel.this.getMarkupId();
}
@Override
protected String manageInputSream(FileUpload upload) {
return UploadPanel.this.onFileUploaded(upload);
}
};
}
public Class extends WebPage> getPageIdentity() {
return UploadIFrame.class;
}
};
uploadIFrame = new InlineFrame("upload", getPage().getPageMap(), iFrameLink);
add(uploadIFrame);
}
/**
* Hackie method allowing to add a javascript in the page defining the
* callback called by the innerIframe
*
*/
private void addOnUploadedCallback() {
final OnUploadedBehavior onUploadBehavior = new OnUploadedBehavior();
add(onUploadBehavior);
add(new WebComponent("onUploaded") {
protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
// calling it through setTimeout we ensure that the callback is called
// in the proper execution context, that is the parent frame
replaceComponentTagBody(markupStream, openTag,
"function onUpload_" + UploadPanel.this.getMarkupId() +
"(clientFileName, newFileUrl) {window.setTimeout(function() { " +
onUploadBehavior.getCallback() + " }, 0 )}");
}
});
}
private class OnUploadedBehavior extends AbstractDefaultAjaxBehavior {
public String getCallback() {
return generateCallbackScript(
"wicketAjaxGet('" + getCallbackUrl(false) +
"&newFileUrl=' + encodeURIComponent(newFileUrl)" +
" + '&clientFileName=' + encodeURIComponent(clientFileName)").toString();
}
protected void respond(AjaxRequestTarget target) {
UploadPanel.this.onUploadFinished(target, getRequest().getParameter("clientFileName"), getRequest().getParameter("newFileUrl"));
}
};
}
UploadPanel.html
<html xmlns:wicket> <wicket:panel> <!-- I put the callback snippet at the body so that is rendered for each panel instead of once --> <script wicket:id="onUploaded" type="text/javascript"></script> <iframe wicket:id="upload" frameborder="0" height="55" width="450" style="overflow:hidden"></iframe> </wicket:panel> </html>UploadIFrame.java
package net.demay.fr.component.ajax.upload;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.model.ResourceModel;
/**
* A webPage to be used in an iframe in order to simulate
* an ajax file upload
* @author doume
*
*/
@SuppressWarnings("serial")
public abstract class UploadIFrame extends WebPage {
private boolean uploaded = false;
private FileUploadField uploadField;
private String newFileUrl;
public UploadIFrame() {
add(new UploadForm("form"));
addOnUploadedCallback();
}
/**
* return the callback url when upload is finished
* @return callback url when upload is finished
*/
protected abstract String getOnUploadedCallback();
/**
* Called when the input stream has been uploaded and when it is available
* on server side
* return the url of the uploaded file
* @param upload fileUpload
*/
protected abstract String manageInputSream(FileUpload upload);
private class UploadForm extends Form {
public UploadForm(String id) {
super(id);
uploadField = new FileUploadField("file");
add(uploadField);
add(new AjaxLink("submit"){
@Override
protected void onClick(AjaxRequestTarget target) {
target.appendJavascript("showProgressWheel()");
}
});
}
public void onSubmit() {
FileUpload upload = uploadField.getFileUpload();
newFileUrl = manageInputSream(upload);
//file is now uploaded, and the IFrame will be reloaded, during
//reload we need to run the callback
uploaded = true;
}
}
private void addOnUploadedCallback() {
//a hacked component to run the callback on the parent
add(new WebComponent("onUploaded") {
protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
if (uploaded) {
if (uploadField.getFileUpload() != null){
replaceComponentTagBody(markupStream, openTag,
"window.parent." + getOnUploadedCallback() + "('" +
uploadField.getFileUpload().getClientFileName() + "','" +
newFileUrl +"')");
}
uploaded = false;
}
}
});
}
}
UploadIframe.html
<html xmlns:wicket>
<head>
<script type="text/javascript">
function showProgressWheel() {
document.images[0].style.display = 'block';
// delay the wheel a bit so it can locally shine
setTimeout(function() { document.forms[0].submit() }, 800);
return false;
}
</script>
</head>
<body>
<form wicket:id="form">
<table>
<tr>
<td>
<input wicket:id="file" type="file"/>
</td>
<td>
<table wicket:id="submit"></table>
</td>
<td>
<img src="indicator.gif" style="display:none"/>
</td>
</tr>
</table>
</form>
<script wicket:id="onUploaded" type="text/javascript">
</body>
</html>
Usage
final UploadPanel upload = new UploadPanel("myUpload"){
@Override
public String onFileUploaded(FileUpload upload) {
if (upload != null){
try {
//save on server side here
//and return the url of the saved file
return savedFile.getUrl()
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return "";
}
@Override
public void onUploadFinished(AjaxRequestTarget target, String filename, String newFileUrl) {
//when upload is finished, will be called
}
};
Power by 

