WICKET-6286 ajax download
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/5578e698 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/5578e698 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/5578e698 Branch: refs/heads/master Commit: 5578e6982b93f81be23c56c8ece90b6f68440eb7 Parents: fd6befd Author: Sven Meier <svenme...@apache.org> Authored: Wed Jan 4 00:00:01 2017 +0100 Committer: Sven Meier <svenme...@apache.org> Committed: Tue Mar 21 23:23:16 2017 +0100 ---------------------------------------------------------------------- .../examples/ajax/builtin/AjaxApplication.java | 2 +- .../examples/ajax/builtin/AjaxDownloadPage.html | 41 +++ .../examples/ajax/builtin/AjaxDownloadPage.java | 244 +++++++++++++++ .../wicket/examples/ajax/builtin/Index.html | 2 + .../wicket/extensions/ajax/AjaxDownload.java | 295 +++++++++++++++++++ .../extensions/ajax/wicket-ajaxdownload.js | 60 ++++ 6 files changed, 643 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java index 871fdbf..dd44b56 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java @@ -57,7 +57,7 @@ public class AjaxApplication extends WicketExampleApplication mountPage("todo-list", TodoList.class); mountPage("world-clock", WorldClockPage.class); mountPage("upload", FileUploadPage.class); - + mountPage("download", AjaxDownloadPage.class); } /** http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html new file mode 100644 index 0000000..2764b25 --- /dev/null +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<html xmlns:wicket="http://wicket.apache.org"> +<head> +<wicket:head> + <style> + .download-veil { + position: absolute; + left: 20%; + right: 20%; + color: white; + background-color: darkorange; + font-size: 40px; + text-align: center; + } + + .download-veil span { + line-height: 128px; + } + </style> +</wicket:head> +</head> +<body> +<wicket:extend> + +<p> +This example demonstrates a <a wicket:id="download">download</a> initiated via Ajax. +</p> + +<p> +This download <a wicket:id="downloadFailure">fails</a>. +</p> + +<p> +A resource reference can be <a wicket:id="downloadReference">used too</a>. +</p> + +<div wicket:id="downloading" class="download-veil"><span>Preparing download ...</span></div> + +</wicket:extend> +</body> +</html> http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java new file mode 100644 index 0000000..a23368c --- /dev/null +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.examples.ajax.builtin; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.extensions.ajax.AjaxDownload; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException; +import org.apache.wicket.request.resource.ContentDisposition; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.request.resource.ResourceReference; +import org.apache.wicket.request.resource.ResourceStreamResource; +import org.apache.wicket.util.resource.IResourceStream; +import org.apache.wicket.util.resource.StringResourceStream; +import org.apache.wicket.util.time.Duration; + +import java.util.concurrent.TimeUnit; + +/** + * Ajax download. + * + * @author svenmeier + */ +public class AjaxDownloadPage extends BasePage +{ + private WebMarkupContainer downloadingContainer; + + /** + * Constructor + */ + public AjaxDownloadPage() + { + downloadingContainer = new WebMarkupContainer("downloading"); + downloadingContainer.setOutputMarkupPlaceholderTag(true); + downloadingContainer.setVisible(false); + add(downloadingContainer); + + initDownload(); + + initDownloadFailure(); + + initDownloadReference(); + } + + private void initDownload() + { + IResource resource = new ResourceStreamResource() { + protected IResourceStream getResourceStream() { + // simulate delay + try + { + TimeUnit.MILLISECONDS.sleep(5000); + } + catch (InterruptedException e) + { + } + + return new StringResourceStream("downloaded via ajax"); + }; + + }.setFileName("File-from-IResource.txt").setContentDisposition(ContentDisposition.ATTACHMENT).setCacheDuration(Duration.NONE); + + final AjaxDownload download = new AjaxDownload(resource) { + + @Override + protected void onBeforeDownload(AjaxRequestTarget target) + { + downloadingContainer.setVisible(true); + target.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); + } + }; + add(download); + + add(new AjaxLink<Void>("download") + { + @Override + public void onClick(AjaxRequestTarget target) + { + download.initiate(target); + } + }); + } + + private void initDownloadFailure() + { + IResource resource = new ResourceStreamResource() { + protected IResourceStream getResourceStream() { + // simulate delay + try + { + TimeUnit.MILLISECONDS.sleep(2000); + } + catch (InterruptedException e) + { + } + + throw new AbortWithHttpErrorCodeException(500); + }; + + }.setFileName("file").setContentDisposition(ContentDisposition.ATTACHMENT).setCacheDuration(Duration.NONE); + + final AjaxDownload download = new AjaxDownload(resource) { + + @Override + protected void onBeforeDownload(AjaxRequestTarget target) + { + downloadingContainer.setVisible(true); + target.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');"); + } + }; + add(download); + + add(new AjaxLink<Void>("downloadFailure") + { + @Override + public void onClick(AjaxRequestTarget target) + { + download.initiate(target); + } + }); + } + + private void initDownloadReference() + { + ResourceReference reference = new ResourceReference("referenceToResource") { + @Override + public IResource getResource() + { + return new StaticResource(); + } + }; + + final AjaxDownload download = new AjaxDownload(reference) { + + @Override + protected void onBeforeDownload(AjaxRequestTarget target) + { + downloadingContainer.setVisible(true); + target.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); + } + }; + add(download); + + add(new AjaxLink<Void>("downloadReference") + { + @Override + public void onClick(AjaxRequestTarget target) + { + download.initiate(target); + } + }); + } + + public static class StaticResource extends ResourceStreamResource { + + StaticResource() { + setFileName("File-from-ResourceReference"); + setContentDisposition(ContentDisposition.ATTACHMENT); + setCacheDuration(Duration.NONE); + } + + @Override + public void respond(Attributes attributes) + { + AjaxDownload.markCompleted(attributes); + + super.respond(attributes); + } + + @Override + protected IResourceStream getResourceStream() + { + // simulate delay + try + { + TimeUnit.MILLISECONDS.sleep(5000); + } + catch (InterruptedException e) + { + } + + return new StringResourceStream("downloaded via ajax with resource reference"); + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html index 4813972..2da2160 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html @@ -35,6 +35,8 @@ <a href="TodoList.html">Todo list Example</a>: shows ajax todo list page without writing any JavaScript <br/><br/> <a href="WorldClockPage.html">World Clock Example</a>: demonstrates a single component with AjaxTimerBehavior updating multiple components +<br/><br/> +<a href="AjaxDownloadPage.html">Ajax Download</a>: download initiated via Ajax </wicket:link> </wicket:extend> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownload.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownload.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownload.java new file mode 100644 index 0000000..a879d5b --- /dev/null +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownload.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.ajax; + +import javax.annotation.Resource; +import javax.servlet.http.Cookie; + +import org.apache.wicket.Component; +import org.apache.wicket.IResourceListener; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.json.JSONObject; +import org.apache.wicket.ajax.json.JsonFunction; +import org.apache.wicket.behavior.Behavior; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.request.Response; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler; +import org.apache.wicket.request.http.WebResponse; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.request.resource.IResource; +import org.apache.wicket.request.resource.IResource.Attributes; +import org.apache.wicket.request.resource.ResourceReference; +import org.apache.wicket.resource.JQueryPluginResourceReference; +import org.apache.wicket.util.lang.Args; + +/** + * Download resources via Ajax. + * <p> + * Usage: + * + * <pre> + * final AjaxDownload download = new AjaxDownload(resource); + * add(download); + * + * add(new AjaxButton("download") + * { + * @Override + * protected void onSubmit(AjaxRequestTarget target, Form<?> form) + * { + * download.initiate(target); + * } + * }); + * </pre> + * + * @author svenmeier + */ +public class AjaxDownload extends AbstractDefaultAjaxBehavior +{ + private static final long serialVersionUID = 1L; + + /** + * Name of parameter used to transfer the download identifier to the resource. + * + * @see #markCompleted(Attributes) + */ + private static final String RESOURCE_PARAMETER_NAME = "wicket-ajaxdownload"; + + private static final ResourceReference JS = new JQueryPluginResourceReference( + AjaxDownload.class, "wicket-ajaxdownload.js"); + + private final ResourceReference resourceReference; + + private final ResourceBehavior resourceBehavior; + + private PageParameters resourceParameters; + + /** + * Download of a {@link Resource}. + * + * @param resource + * resource to download + */ + public AjaxDownload(IResource resource) + { + Args.notNull(resource, "resource"); + this.resourceBehavior = new ResourceBehavior(resource); + this.resourceReference = null; + } + + /** + * Download of a {@link ResourceReference}. + * <p> + * The {@link IResource} returned by {@link ResourceReference#getResource()} must call + * {@link #markCompleted(Attributes)} when responding, otherwise the callback + * {@link #onDownloadSuccess(AjaxRequestTarget)} will not work. + * + * @param reference + * reference to resource to download + */ + public AjaxDownload(ResourceReference reference) + { + this(reference, null); + } + + /** + * Download of a {@link ResourceReference}. + * <p> + * The {@link IResource} returned by {@link ResourceReference#getResource()} must call + * {@link #markCompleted(Attributes)} when responding, otherwise the callback + * {@link #onDownloadSuccess(AjaxRequestTarget)} will not work. + * + * @param reference + * reference to resource to download + * @param resourceParameters + * parameters for the resource + */ + public AjaxDownload(ResourceReference reference, PageParameters resourceParameters) + { + this.resourceBehavior = null; + + this.resourceReference = Args.notNull(reference, "reference"); + this.resourceParameters = resourceParameters; + } + + @Override + protected void onBind() + { + super.onBind(); + + if (resourceBehavior != null) + { + getComponent().add(resourceBehavior); + } + } + + @Override + protected void onUnbind() + { + super.onUnbind(); + + if (resourceBehavior != null) + { + getComponent().remove(resourceBehavior); + } + } + + /** + * Call this method to initiate the download. + * + * @param target + * the initiating Ajax target + */ + public void initiate(AjaxRequestTarget target) + { + if (getComponent() == null) + { + throw new WicketRuntimeException("not bound to a component"); + } + + ((WebResponse)RequestCycle.get().getResponse()).clearCookie(cookie(getName())); + + CharSequence url; + if (resourceBehavior == null) + { + if (resourceReference.canBeRegistered()) + { + getComponent().getApplication().getResourceReferenceRegistry() + .registerResourceReference(resourceReference); + } + + PageParameters parameters = new PageParameters(); + if (resourceParameters != null) + { + parameters.mergeWith(resourceParameters); + } + parameters.set(RESOURCE_PARAMETER_NAME, getName()); + + url = getComponent().getRequestCycle() + .urlFor(new ResourceReferenceRequestHandler(resourceReference, parameters)); + } + else + { + url = resourceBehavior.getUrl(); + } + + JSONObject settings = new JSONObject(); + settings.put("attributes", new JsonFunction(renderAjaxAttributes(getComponent()))); + settings.put("name", getName()); + settings.put("url", url); + + target.appendJavaScript(String.format("Wicket.AjaxDownload.initiate(%s);", settings)); + + onBeforeDownload(target); + } + + protected void onBeforeDownload(AjaxRequestTarget target) + { + } + + protected void onDownloadSuccess(AjaxRequestTarget target) + { + } + + protected void onDownloadFailed(AjaxRequestTarget target) + { + } + + @Override + public void renderHead(Component component, IHeaderResponse response) + { + super.renderHead(component, response); + + response.render(JavaScriptHeaderItem.forReference(JS)); + } + + @Override + protected void respond(AjaxRequestTarget target) + { + String result = getComponent().getRequest().getRequestParameters().getParameterValue("result").toOptionalString(); + if ("success".equals(result)) { + onDownloadSuccess(target); + } else if ("failed".equals(result)) { + onDownloadFailed(target); + } + } + + /** + * Identifying name of this behavior. + */ + private String getName() + { + return String.format("wicket-ajaxdownload-%s-%s", getComponent().getMarkupId(), + getComponent().getBehaviorId(this)); + } + + /** + * The behavior responding with the actual resource. + */ + private class ResourceBehavior extends Behavior implements IResourceListener + { + private final IResource resource; + + private ResourceBehavior(IResource resource) + { + this.resource = Args.notNull(resource, "resource"); + } + + @Override + public void onResourceRequested() + { + final RequestCycle requestCycle = RequestCycle.get(); + final Response response = requestCycle.getResponse(); + ((WebResponse) response).addCookie(cookie(getName())); + + Attributes a = new Attributes(requestCycle.getRequest(), response, null); + + resource.respond(a); + } + + public CharSequence getUrl() + { + return getComponent().urlFor(this, IResourceListener.INTERFACE, null); + } + } + + /** + * Mark a resource as complete. + * <p> + * Has to be called from {@link IResource#respond(Attributes)} when downloaded via + * {@link #AjaxDownload(IResource)}. + * + * @param attributes + * resource attributes + */ + public static void markCompleted(IResource.Attributes attributes) + { + String cookieName = attributes.getParameters().get(RESOURCE_PARAMETER_NAME).toString(); + + ((WebResponse)attributes.getResponse()).addCookie(cookie(cookieName)); + } + + private static Cookie cookie(String name) + { + Cookie cookie = new Cookie(name, "complete"); + cookie.setPath("/"); + return cookie; + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/5578e698/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js new file mode 100644 index 0000000..52471df --- /dev/null +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +;(function (undefined) { + 'use strict'; + + if (!window.Wicket) { + window.Wicket = {}; + } + + if (Wicket.AjaxDownload) { + return; + } + + Wicket.AjaxDownload = { + initiate : function(settings) { + + var frame = jQuery("<iframe>").hide().prop("src", settings.url).appendTo("body"); + + var checkComplete = function() { + var result; + + if (document.cookie.indexOf(settings.name + '=') > -1) { + result = "success"; + } else { + var html = frame.contents().find('body').html(); + if (html && html.length) { + result = "failed"; + } + } + + if (result) { + setTimeout(function() { frame.remove(); }, 0); + + settings.attributes.ep = settings.attributes.ep || {}; + settings.attributes.ep['result'] = result; + Wicket.Ajax.ajax(settings.attributes); + } else { + setTimeout(checkComplete, 100); + } + }; + + checkComplete(); + } + }; + +})(); \ No newline at end of file