First of all, we need to know upload is not supported by XmlHttpRequest so if we want to do ajax upload we need to use an Iframe. This iframe will upload the file and then notify the parent window with javascript. Here is a way to do that with 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 } };