I have a form which, onSubmit
, requests some input-dependent data from a server and creates a file and a ResourceStream
of it.
The file is rather important, so the download should start immediately. But I can't allow multiple requests/submits to happen, so I want to switch the page or replace the panel at the same time.
Is there a way to do this? Download the file and switch page/panel in a single click?
So far, I create a ResourceStreamRequestHandler handler
and use getRequestCycle().scheduleRequestHandlerAfterCurrent(handler)
to initiate the download during onSubmit
.
protected void onSubmit() {
// Get the data.
// ...
ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(
getResourceStream(data...), getFileName(data...));
getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
//oldPanel.this.replaceWith(new newPanel(data...));
//setResponsePage(mainPage.class);
}
The problem is, if I use setResponsePage
to return to the main page, the download does not start, and if I try to replaceWith(new newPanel())
, the download starts, but the panel is not properly replaced. I even tried to initiate the download from newPanel
(e.g. during onAfterRender
, etc.), but the panels still weren't swapped correctly.
Thanks to martin-g pointing me in the right direction, I finally figured it out.
Using org.apache.wicket.extensions.ajax.AjaxDownloadBehavior
is the way to go, but martin-g's answer lacks some crucial information:
AjaxDownloadBehavior
cannot be added "on the fly", as it relies on JavaScript being incorporated into the header on page/panel load. So it should be added in the constructor.AjaxDownloadBehavior
does not operate on models and neither it nor the IResource
/ResourceReference
objects it takes as parameters (as far as I could see) allow for dynamic changes to the download resource, one must create a custom IResource
or ResourceReference
class for that.An example to demonstrate the idea:
// Global instance of the custom IResource class, for easier handling.
private final DynamicResourceStreamResource downloadResource = new DynamicResourceStreamResource();
// Custom IResource class for dynamic resource generation/swapping.
private class DynamicResourceStreamResource extends ResourceStreamResource {
private IResourceStream stream;
public DynamicResourceStreamResource() {
this.stream = null;
}
// This right here allows to swap the ResourceStream dynamically later.
public void setStream(IResourceStream stream) {
this.stream = stream;
}
@Override
protected IResourceStream getResourceStream(
IResource.Attributes attributes) {
return this.stream;
}
}
// Panel constructor.
DownloadPanel(String wicketId) {
super(wicketId);
FeedbackPanel feedback = new FeedbackPanel("feedback");
add(feedback);
feedback.setOutputMarkupPlaceholderTag(true);
AjaxDownloadBehavior download = new AjaxDownloadBehavior(downloadResource) {
@Override
protected void onDownloadSuccess(AjaxRequestTarget target) {
// Redirect after successful download.
setResponsePage(mainPage.class);
}
};
add(download);
Form<Void> form = new Form<Void>("form");
add(form);
form.setOutputMarkupPlaceholderTag(true);
form.add(new AjaxButton("formSubmitButton") {
@Override
protected void onError(AjaxRequestTarget target) {
super.onError(target);
// Reload form; required, if e.g. input fields are cleared on error.
target.add(form);
// Reload feedback panel; required to display validator messages.
target.add(feedback);
}
@Override
protected void onSubmit(AjaxRequestTarget target) {
// Get the data.
// ...
downloadResource.setStream(getResourceStream(data...));
downloadResource.setFileName(getFileName(data...));
/*
* Download could also be initiated onAfterSubmit, leaving the data
* collection and processing to the form itself.
*/
download.initiate(target);
}
}
}
Still, I'll have to abandon this approach, as the form validation, as I would expect and prefer it (e.g. tooltips popping up, directing the user to required fields), doesn't seem to work for me with Ajax components.