I’ve decided to start a new series of ‘quick tip’ posts. Each post will be short and focused on a particular problem, an interesting technique, or to improve your productivity.
Update: As was pointed out to me by Daniel Stoch (thank you) the implementation in this demo is not truly async, it was a poor choice of title. It’s possible to do but more complex. What I really meant is it doesn’t block the UI, the user can still scroll/click on things, etc.
What we want to do
A typical use-case in any webapp is generating and downloading data in CSV format. You can think of various reports: transactions, analytics, user accounts, etc. We want to be able to asynchronously generate the report in response to a user click.
How to do it
We are going to implement a generic ajax download behavior that we can attach to buttons and links. It won’t be concerned with the content of the data, but rather how to get to the browser:
public abstract class AJAXDownload extends AbstractAjaxBehavior {
public AJAXDownload() {
}
public void initiate(AjaxRequestTarget target) {
String url = getCallbackUrl().toString();
url = url + (url.contains("?") ? "&" : "?");
url = url + "antiCache=" + System.currentTimeMillis();
target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);");
}
public void onRequest() {
ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(getResourceStream(),getFileName());
handler.setContentDisposition(ContentDisposition.ATTACHMENT);
getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
}
protected String getFileName() { return null; }
protected abstract IResourceStream getResourceStream();
}
Implementations of this class will override getResourceStream() to provide the actual content of the export. The initiate() method will be called to send the results to the browser.
How to use it
That was simple enough! Let’s look at how we would use it:
public class AjaxDownloadTestPage extends BasePage implements IBasePage {
private AJAXDownload download;
public AjaxDownloadTestPage() {
super();
Form<Void> testForm = new Form<Void>("testForm");
addOrReplace(testForm);
download = new AJAXDownload() {
@Override
protected IResourceStream getResourceStream() {
String csvContents = "id,first,last\n1,Roman,Sery\n2,Mike,Wicket\n";
return new StringResourceStream(csvContents, "text/csv");
}
@Override protected String getFileName() { return "export.csv"; }
};
testForm.add(new SingleClickAjaxButton("singleClickBtn", testForm, true, null) {
@Override protected void onSubmit(AjaxRequestTarget target) { download.initiate(target); }
}.add(download));
}
}
We created a button that initiates the download when clicked by adding the AJAXDownload behavior to it. In the implementation of the behavior, you would generate the CSV contents using some robust service methods instead of the dummy data you see here.
And that’s all there is to it! Full source code here. Hope you have enjoyed this quick tip.