Wicket – Ajax like file upload on a modal window
34 Comments »
A lot of people seem to be asking how to do a file upload using AJAX. The short answer to this is that it cannot be done! That’s because it is not supported by today’s HTML / browser specs.
What you can do is just fake it
.
I’ve been using Wicket (Java Web Framework) regularly for web development. Recently I needed to be able to do a file upload inside a modal window. File uploading needs form submission in order to work so I faced a few problems:
- My modal window was implemented as a panel. I could not do a regular form submission as that would close (submit) my modal window. I’ve been suggested to implement the modal window as a page instead of the panel so that I could use Ajax submit but I really needed it the way it was.
- Ajax and multipart don’t cope.
- Whatever I tried, without regular submission, the file uploading field would not return the file name so it was impossible to get.
I searched the web and I finally found and implemented the solution. It involves using an iframe, but it’s pretty simple. Here’s the code if anyone else needs this:
- UploadIFrame.java
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
public void onClick(AjaxRequestTarget target) {
target.appendJavascript("showProgressWheel()");
}
});
}
@Override
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") {
@Override
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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns:wicket> <head> <link href = "../counters/css/style.css" type = "text/css" rel = "stylesheet"/> <!--[if IE]> <link href = "../counters/css/ie.css" type = "text/css" rel = "stylesheet"/> <![endif]--> <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 style="width:500px; margin-top:0px;"> <tr> <td> <input style="width:auto;" wicket:id="file" type="file"/> </td> <td> <a wicket:id="submit">Change Avatar</a> </td> <td> <img src="images/indicator.gif" style="display:none"/> </td> </tr> </table> </form> <script wicket:id="onUploaded" type="text/javascript"></script> </body> </html>
- UploadPanel.java
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);
@Override
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() {
@Override
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);
}
};
}
@Override
public Class<UploadIFrame> 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") {
@Override
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) +
"&amp;newFileUrl=' + encodeURIComponent(newFileUrl)" +
" + '&amp;clientFileName=' + encodeURIComponent(clientFileName)").toString();
}
@Override
protected void respond(AjaxRequestTarget target) {
UploadPanel.this.onUploadFinished(target, getRequest().getParameter("clientFileName"), getRequest().getParameter("newFileUrl"));
}
};
}
- UploadPanel.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <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" style="height:60px; width:500px; overflow:hidden"></iframe> </wicket:panel> </html>
- This is how to use it:
add(new UploadPanel("myUpload"){
@Override
public String onFileUploaded(FileUpload upload) {
if (upload != null){
if(upload.getSize() > 1000000) {
return "The Picture Is Too Big !";
} else {
try {
jdbcSignUp.changeAvatarFor(((WebSession)getSession()).getUser().getUsername(), upload.getInputStream(), (int)upload.getSize());
//save on server side here
//and return the url of the saved file
return "The Picture Has Been Uploaded";
} catch (IOException e) {
log.error("Exception:", e);
}
}
}
return "";
}
@Override
public void onUploadFinished(AjaxRequestTarget target, String filename, String newFileUrl) {
//when upload is finished, will be called
messageLabel.setModelObject(newFileUrl);
target.addComponent(messageLabel);
avatarImage.setImageResource(new MyBlobImageResource(((WebSession)getSession()).getUser().getUsername()));
target.addComponent(avatarImage);
((BasePage)parent).onAvatarChanged(target);
}
});
Most credit should go to Vincent Demay, although his code contains errors.





By Andrew on May 2, 2008 | Reply
Thanks for the fixed version.
I’m still having a little problem.
The file uploads file and then triggers the onUploadFinished, but I seem to find that the filename passed into the method is always null.
Would you have any idea why this might be?
By admin on May 4, 2008 | Reply
No, I retested and it works for me. I always get the filename, never null.
By the way, if you’re looking for the filepath, you have to play with the wicket sources I think because the client’s path is encapsulated in the FileUpload class.
On a regular use case though, you should not even need it since you have access to the input stream.
By geke on Jun 3, 2008 | Reply
Your solution works very fine, but in relation with an LinkTree there are problems. Maybe you can help me.
I have a LinkTree on my site. But after closing the modal window, the tree models userObject has a reference, but the attributes of the userObject are all null. Hence after the onClicked event on nodes in my tree, there are no Nodes visible. That is even the modal window is closed without a fileupload.
By admin on Jun 4, 2008 | Reply
I haven’t tested it with a tree, but I use it in a lot of projects and it works just fine.
I’m guessing it must be a little bug somewhere in Wicket when using a tree and an iframe.
By geke on Jun 4, 2008 | Reply
Thank you for you answer.
Today I find the solution.
Now it works fine.
It was my mistake with programming the tree.
By admin on Jun 4, 2008 | Reply
I’m glad it worked out for you!
By Mathias Nilsson on Aug 16, 2008 | Reply
I get this error all the time
Exception evaluating javascript: ReferenceError: Can’t find variable: showProgressWheel
By Siddharth on Aug 21, 2008 | Reply
Hi,
First of all thanks for putting this code. This has been extremely helpful. I have few questions though..These might be simple questions but any help is appreciated.
1. I also have implemented Modal Window as Panel (i.e. I add Panel to Modal Window in this case UploadPanel) but, the problem is IFrame looks bad in Modal window as it either verflows from modal window or is always smaller than Modal window. Is there a way to synchronize the size of Modal window and IFrame inside it so that user cannot tell IFrame from Modal Window.
2. Also, in my case File upload field is required and user should be able to close the pop-up by clicking a close link inside the pop-up. No, problem here..I put Link here and did a modal.close(target) from UploadPanel. But this causes form to validate and it shows validation errors when user clicks it for the first time. However, if uses clicks it second time the modal window is closed. Do you know how to get around this ? I also tried AjaxFallbackButton to close Modal window but it causes “Null Pointer Exception”. Then I read on Nabble (wicket forum) that I cannot close enclosing Modal from IFrame as this is considered Cross Browser scripting.
By admin on Aug 21, 2008 | Reply
Hi,
1. I used CSS to make the IFrame as I needed it to be inside the modal. No magic here.
2. I also had a use case where the upload file field was required, so I did the validation myself.
When the user pressed the close button on the “upload panel” or the X of the modal window (I implemented the callback), on the onSubmit I check if the upload field is blank. If not, I called a method in the class that created the modal to close it. Else show an error message.
By Bonita on Oct 29, 2008 | Reply
Thanks for writing this.
By James on Nov 4, 2008 | Reply
For the life of me I cannot get this to work on Wicket 1.3.5 and in a modal window. I’ve been trying for two whole days. Do you think you could package up some working code and attach it for us to download and experiment with? Ie a complete example?
My issue is that nothing is happening past the click of the link to submit the form.
By James on Dec 18, 2008 | Reply
Nevermind, I had an issue with a filter eating my upload request. I moved to a different strategy anyway.
By admin on Dec 18, 2008 | Reply
Hello, James. I’m sorry I didn’t get to read your message earlier.
No problem, I can prepare a complete example for you. But all there is to it is in the code above.
By Cédric on Dec 19, 2008 | Reply
Thanks for this.
I have a strange problem: I close my modal in the onUploadFinished method and I get this message:
org.apache.wicket.RequestListenerInterface.invoke:174 – component not enabled or visible; ignoring call.
Any ideas ?
By admin on Jan 8, 2009 | Reply
@Cedric
If you still need an answer for that, please comment here again, leaving you email address in the field above the comments text box and I will write back very soon.
By Cédric on Jan 21, 2009 | Reply
Thanks
I use this into in ModalWindow and everything is fine until and close the modal.
It seems to work when I use it without the Modal…
Maybe it want to refresh something form the iframe that is not visible anymore?
Cedric – thiebal@yahoo.fr
By Cédric on Jan 21, 2009 | Reply
Ok it works when I use the Modal as an Iframe (with a page) and not as a div (with a panel).
By Bill on Feb 18, 2009 | Reply
I’ve been banging my head against this for hours now and nothing. It doesn’t work. All I get is the onclick event when clicking on the Submit link. That’s it. What am I missing?
Could you post a working example that can be downloaded? I bet that would make a lot of people happy.
By Daniel Ferreira Castro on Mar 27, 2009 | Reply
On the section “This is how to use it” there are some fields not defined, such as:
- jdbcSignUp
- avatarImage
- MyBlobImageResource
What are they?
By Daniel Ferreira Castro on Mar 27, 2009 | Reply
Maybe a jar containing the sourcecode complete for coompilation would be easier to adapt.
By ji on Apr 17, 2009 | Reply
Thank you for your code,
I am a new user for wicket, I add this code in my application, and put in my html, it occurs javascript error, i.e. http://192.168.0.40:8080/xx/app/?wicket:interface=:2:myUpload:upload::ILinkListener::
so it is no response when clicking on “Change Avatar” link.
would u mind share your expirence?
Thank you in advance.
By ji on Apr 17, 2009 | Reply
it’s work fine now.
Thank you very much
By Jeremy on May 21, 2009 | Reply
Hi,
Great code.
One question – once the file has uploaded, then the iframe is re-rendered causing javascript to be placed the “onUploaded” component in the iFrame. This calls the parent object’s onUpload_() javascript function that issues an ajax request to the OnUploadedBehavior’s respond() method?
Reason I ask is because debugging my code via Eclipse shows that the respond() method is never called.
Many thanks
Jeremy
By Jeremy on May 21, 2009 | Reply
Hi,
Another quick one – the iFrame class’s manageInputSream() calls the parent UploadPanel’s onFileUploaded() method which gives us a ref. to the uploaded file (NB uploaded to java.io.tmpdir which is a folder on my tomcat) from where we can do what we want with it. So, why do we need the OnUploadedBehavior to also notify us? We know the file has been uploaded already…I apologise if i’m missing something obvious.
Many thanks for your time
Jeremy
By admin on May 21, 2009 | Reply
Hello Jeremy,
Considering I’ve been getting a lot of feedback on this post, I’m planning on doing a new post for this Ajax file upload using the Modal Window Wicket component.
I realize the code in this post cannot be used as is so I will upload a wicket component designed to be used in all sorts of situations, that people can download and play with. I will answer your questions in that post.
I think it will be available later on this week, depending on the work load I have.
Cheers
By Jeremy on May 23, 2009 | Reply
Hey,
V. quickly – can you email me your example code zipped?? Reason i ask is because when i cut ‘n pasted the code above there are some HTML entities in the OnUploadedBehavior() inner class’s generateCallbackScript() method which I think have doubles/inaccuracies due to some ‘escaping’ issue when displaying your HTML in this web page. This could be why the resulting Ajax request once the file has uploaded is not reaching the inner class’s respond() method…
Cheers again
Jeremy
By Jeremy on May 27, 2009 | Reply
Hey,
Sorry to disturb you again.
I have an extra field to your upload form. Once upload has completed I want to reset the form’s values. At its simplest I could reload the iFrame but i’m not sure how to do this… any pointers?
Cheers
Jeremy
By Jeremy on Jun 1, 2009 | Reply
Hey,
I solved all my issues – last one
There seem to be 2 requests from the browser when displaying the iframe so the “onUploaded” WebComponent.onComponentTagBody() gets called twice!
This also happens after clicking the ajaxlink’s submit link. The UploadIFrame.onsubmit() is called once, but the WebComponent.onComponentTagBody() gets called twice again because the browser is issueing 2 requests!!
I can’t see any re-directs happening…
Have you seen this double-request behaviour regarding iframes before?
Thanks
Jeremy
By Martin Sherwood on Jun 8, 2009 | Reply
I experienced the same problem as Andrew mentions in the first comment, namely that the parameter values for filename and newFileUrl were always null in the onUploadFinished method.
Replacing the “&” with “&” in the generateCallbackScript method in the inner class OnUploadedBehavior of UploadPanel solved it for me.
Cheers,
Martin
By blue on Jun 28, 2009 | Reply
@Martin
Thanks!!! Replacing the & –> & worked for me too! I have been tracing around this thing for the past 2 hours trying to figure out what was going on. You saved me quite a bit of time, thanks.
By mark on Aug 19, 2009 | Reply
Hi, I am new with the Wicket. I wanted to implement a file upload form in to panel. You wrote it was not recommended. However I want to check it out for sure because I have a tabbed panel and on the one of them I want to put file upload form. Is it possible and do you know any samples ?
thanks
mark
By Wolf19 on Oct 11, 2009 | Reply
In looking at your site I say an even stronger thank you. ,
By msj121 on Jan 17, 2010 | Reply
Hey,
I seem to always get a page expired error when opening my modal window containing the uploadpanel (without any interaction). The generated code:
Video
Name:
Category:
Choose One
Home
Videos
function onUpload_fileUploader345(clientFileName, newFileUrl) {window.setTimeout(function() { var wcall=wicketAjaxGet(’?wicket:interface=:53:editoraddholder:editoraddvideo:ajaxform:fileUploader::IBehaviorListener:0:&newFileUrl=’ + encodeURIComponent(newFileUrl) + ‘&&clientFileName=’ + encodeURIComponent(clientFileName),null,null, function() {return Wicket.$(’fileUploader345′) != null;}.bind(this)); }, 0 )}
Description:
Date Added: 1/17/10
Views: 0 Rating: 0
Submit Cancel
By msj121 on Jan 17, 2010 | Reply
btw, it seems to have to do with the iframe… I also have a weird feeling it might have to do with the pagelink, not sure though….
anyone see this before?