Wicket 8: How to start a download and switch page/replace panel at the same time?

58 Views Asked by At

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.

2

There are 2 best solutions below

0
Michael Burgardt On BEST ANSWER

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:

  1. The 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.
  2. Since 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.

3
martin-g On

You can use `org.apache.wicket.extensions.ajax.AjaxDownloadBehavior.

private void initDownloadInNewWindow()
{
    IResource resource = new ExampleResource("downloaded via ajax in a new browser window")
        .setContentDisposition(ContentDisposition.INLINE);

    final AjaxDownloadBehavior download = new AjaxDownloadBehavior(resource)
    {
        private static final long serialVersionUID = 1L;

        @Override
        protected void onBeforeDownload(IPartialPageRequestHandler handler)
        {
            downloadingContainer.setVisible(true);
            handler.add(downloadingContainer);
        }

        @Override
        protected void onDownloadSuccess(AjaxRequestTarget target)
        {
            downloadingContainer.setVisible(false);
            target.add(downloadingContainer);
        }

        @Override
        protected void onDownloadFailed(AjaxRequestTarget target)
        {
            downloadingContainer.setVisible(false);
            target.add(downloadingContainer);

            target.appendJavaScript("alert('Download failed');");
        }

        @Override
        protected void onDownloadCompleted(AjaxRequestTarget target)
        {
            downloadingContainer.setVisible(false);
            target.add(downloadingContainer);
        }
    };
    download.setLocation(AjaxDownloadBehavior.Location.NewWindow);
    add(download);

    add(new AjaxLink<Void>("downloadInNewWindow")
    {
        private static final long serialVersionUID = 1L;

        @Override
        public void onClick(AjaxRequestTarget target)
        {
            download.initiate(target);
        }
    });
}

See https://examples9x.wicket.apache.org/ajax/download for example. Its source code could be found at https://github.com/apache/wicket/blob/master/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java

You will need to trigger the download in a new window/tab and then call setResponsePage(NewPage.class) to do the redirect.