You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by so...@apache.org on 2017/09/22 17:23:04 UTC

[27/50] [abbrv] wicket git commit: WICKET-6460 Rename AjaxDownload to AjaxDownloadBehavior

WICKET-6460 Rename AjaxDownload to AjaxDownloadBehavior


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/1fc1e3d7
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/1fc1e3d7
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/1fc1e3d7

Branch: refs/heads/WICKET-6105-java.time
Commit: 1fc1e3d7040ed2f3e73e7067efd201a958a90b3e
Parents: 1f805dd
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Mon Sep 4 22:26:30 2017 +0300
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Mon Sep 4 22:26:30 2017 +0300

----------------------------------------------------------------------
 .../examples/ajax/builtin/AjaxDownloadPage.java |  22 +-
 .../wicket/extensions/ajax/AjaxDownload.java    | 384 ------------------
 .../extensions/ajax/AjaxDownloadBehavior.java   | 388 +++++++++++++++++++
 3 files changed, 399 insertions(+), 395 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/1fc1e3d7/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
index 6d1aa3e..70a682d 100644
--- 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
@@ -20,8 +20,8 @@ import java.util.concurrent.TimeUnit;
 
 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.extensions.ajax.AjaxDownload.Location;
+import org.apache.wicket.extensions.ajax.AjaxDownloadBehavior;
+import org.apache.wicket.extensions.ajax.AjaxDownloadBehavior.Location;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException;
 import org.apache.wicket.request.resource.ContentDisposition;
@@ -76,7 +76,7 @@ public class AjaxDownloadPage extends BasePage
 		IResource resource = new ExampleResource("downloaded via ajax")
 			.setContentDisposition(ContentDisposition.ATTACHMENT);
 		
-		final AjaxDownload download = new AjaxDownload(resource) {
+		final AjaxDownloadBehavior download = new AjaxDownloadBehavior(resource) {
 			
 			@Override
 			protected void onBeforeDownload(AjaxRequestTarget target)
@@ -118,7 +118,7 @@ public class AjaxDownloadPage extends BasePage
 		IResource resource = new ExampleResource("downloaded via ajax in iframe")
 			.setContentDisposition(ContentDisposition.ATTACHMENT);
 		
-		final AjaxDownload download = new AjaxDownload(resource) {
+		final AjaxDownloadBehavior download = new AjaxDownloadBehavior(resource) {
 			
 			@Override
 			protected void onBeforeDownload(AjaxRequestTarget target)
@@ -166,7 +166,7 @@ public class AjaxDownloadPage extends BasePage
 			}
 		};
 		
-		final AjaxDownload download = new AjaxDownload(reference) {
+		final AjaxDownloadBehavior download = new AjaxDownloadBehavior(reference) {
 			
 			@Override
 			protected void onBeforeDownload(AjaxRequestTarget target)
@@ -208,7 +208,7 @@ public class AjaxDownloadPage extends BasePage
 		IResource resource = new ExampleResource("downloaded via ajax in a new browser window")
 			.setContentDisposition(ContentDisposition.INLINE);
 
-		final AjaxDownload download = new AjaxDownload(resource) {
+		final AjaxDownloadBehavior download = new AjaxDownloadBehavior(resource) {
 
 			@Override
 			protected void onBeforeDownload(AjaxRequestTarget target)
@@ -240,7 +240,7 @@ public class AjaxDownloadPage extends BasePage
 				target.add(downloadingContainer);
 			}
 		};
-		download.setLocation(AjaxDownload.Location.NewWindow);
+		download.setLocation(AjaxDownloadBehavior.Location.NewWindow);
 		add(download);
 
 		add(new AjaxLink<Void>("downloadInNewWindow")
@@ -258,7 +258,7 @@ public class AjaxDownloadPage extends BasePage
 		IResource resource = new ExampleResource("downloaded via ajax in same browser window")
 			.setContentDisposition(ContentDisposition.ATTACHMENT);
 
-		final AjaxDownload download = new AjaxDownload(resource) {
+		final AjaxDownloadBehavior download = new AjaxDownloadBehavior(resource) {
 
 			@Override
 			protected void onBeforeDownload(AjaxRequestTarget target)
@@ -290,7 +290,7 @@ public class AjaxDownloadPage extends BasePage
 				target.add(downloadingContainer);
 			}
 		};
-		download.setLocation(AjaxDownload.Location.SameWindow);
+		download.setLocation(AjaxDownloadBehavior.Location.SameWindow);
 		add(download);
 
 		add(new AjaxLink<Void>("downloadInSameWindow")
@@ -314,7 +314,7 @@ public class AjaxDownloadPage extends BasePage
 		@Override
 		public void respond(Attributes attributes)
 		{
-			AjaxDownload.markCompleted(attributes);
+			AjaxDownloadBehavior.markCompleted(attributes);
 			
 			super.respond(attributes);
 		}
@@ -370,4 +370,4 @@ public class AjaxDownloadPage extends BasePage
 		};
 
 	}
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/1fc1e3d7/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
deleted file mode 100644
index 370b77a..0000000
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownload.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * 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 java.util.Locale;
-
-import javax.annotation.Resource;
-import javax.servlet.http.Cookie;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.IRequestListener;
-import org.apache.wicket.WicketRuntimeException;
-import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-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.ContentDisposition;
-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;
-
-import com.github.openjson.JSONObject;
-
-/**
- * Download resources via Ajax.
- * <p>
- * Usage:
- * 
- * <pre>
- * final AjaxDownload download = new AjaxDownload(resource);
- * add(download);
- * 
- * add(new AjaxButton("download")
- * {
- * 	&#64;Override
- * 	protected void onSubmit(AjaxRequestTarget target, Form&lt;?> form)
- * 	{
- * 		download.initiate(target);
- * 	}
- * });
- * </pre>
- * 
- * @author svenmeier
- * @author Martin Grigorov
- * @author Maxim Solodovnik
- */
-public class AjaxDownload extends AbstractDefaultAjaxBehavior
-{
-	private static final long serialVersionUID = 1L;
-
-	public enum Location {
-		/**
-		 * The resource will be downloaded into a {@code blob}.
-		 * <p>
-		 * This is recommended for modern browsers.
-		 */
-		Blob,
-
-		/**
-		 * The resource will be downloaded via a temporary created iframe, the resource has to be a
-		 * {@link ContentDisposition#ATTACHMENT}.
-		 * <p>
-		 * This is recommended when there are resources in the DOM which will be
-		 * closed automatically on JavaScript <em>unload</em> event, like WebSockets.
-		 * Supports both <em>success</em> and <em>failure</em> callbacks!
-		 */
-		IFrame,
-
-		/**
-		 * The resource will be downloaded by changing the location of the current DOM document,
-		 * the resource has to be a {@link ContentDisposition#ATTACHMENT}.
-		 * <p>
-		 * Note: This will trigger JavaScript <em>unload</em> event on the page!
-		 * Does not support {@link AjaxDownload#onDownloadFailed(AjaxRequestTarget)} callback,
-		 * i.e. it is not possible to detect when the download has failed!
-		 */
-		SameWindow,
-
-		/**
-		 * The resource will be downloaded in a new browser window by using JavaScript <code>window.open()</code> API,
-		 * the resource has to be a {@link ContentDisposition#INLINE}.
-		 */
-		NewWindow
-	}
-
-	/**
-	 * 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;
-
-	private Location location = Location.Blob;
-
-	/**
-	 * 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("downloadUrl", url);
-		settings.put("method", getLocation().name().toLowerCase(Locale.ENGLISH));
-
-		target.appendJavaScript(String.format("Wicket.AjaxDownload.initiate(%s);", settings));
-
-		onBeforeDownload(target);
-	}
-
-	protected void onBeforeDownload(AjaxRequestTarget target)
-	{
-	}
-
-	/**
-	 * A callback executed when the download of the resource finished successfully.
-	 *
-	 * @param target The Ajax request handler
-	 */
-	protected void onDownloadSuccess(AjaxRequestTarget target)
-	{
-	}
-
-	/**
-	 * A callback executed when the download of the resource failed for some reason,
-	 * e.g. an error at the server side.
-	 * <p>
-	 * Since the HTTP status code of the download is not available to Wicket, any HTML in the resource response
-	 * will be interpreted as a failure HTTP status message. Thus is it not possible to download HTML resources
-	 * via {@link AjaxDownload}.
-	 *
-	 * @param target The Ajax request handler
-	 */
-	protected void onDownloadFailed(AjaxRequestTarget target)
-	{
-	}
-
-	/**
-	 * A callback executed when the download of the resource finished successfully or with a failure.
-	 *
-	 * @param target The Ajax request handler
-	 */
-	protected void onDownloadCompleted(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);
-		}
-		onDownloadCompleted(target);
-	}
-
-	public Location getLocation() {
-		return location;
-	}
-
-	public AjaxDownload setLocation(final Location location) {
-		this.location = Args.notNull(location, "location");
-		return this;
-	}
-
-	/**
-	 * 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 IRequestListener
-	{
-		private final IResource resource;
-
-		private ResourceBehavior(IResource resource)
-		{
-			this.resource = Args.notNull(resource, "resource");
-		}
-
-		@Override
-		public boolean rendersPage()
-		{
-			return false;
-		}
-		
-		@Override
-		public void onRequest()
-		{
-			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().urlForListener(this, 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");
-		
-		// has to be on root, otherwise JavaScript will not be able to access the
-		// cookie when it is set from a different path - which is the case when a
-		// ResourceReference is used
-		cookie.setPath("/");
-		
-		return cookie;
-	}
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/1fc1e3d7/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownloadBehavior.java
----------------------------------------------------------------------
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownloadBehavior.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownloadBehavior.java
new file mode 100644
index 0000000..787c826
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/AjaxDownloadBehavior.java
@@ -0,0 +1,388 @@
+/*
+ * 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 java.util.Locale;
+
+import javax.annotation.Resource;
+import javax.servlet.http.Cookie;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.IRequestListener;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+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.ContentDisposition;
+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;
+
+import com.github.openjson.JSONObject;
+
+/**
+ * Download resources via Ajax.
+ * <p>
+ * Usage:
+ * 
+ * <pre>
+ * final AjaxDownload download = new AjaxDownload(resource);
+ * add(download);
+ * 
+ * add(new AjaxButton("download")
+ * {
+ * 	&#64;Override
+ * 	protected void onSubmit(AjaxRequestTarget target, Form&lt;?> form)
+ * 	{
+ * 		download.initiate(target);
+ * 	}
+ * });
+ * </pre>
+ *
+ * <p>To set the name of the downloaded resource make use of
+ * {@link org.apache.wicket.request.resource.ResourceStreamResource#setFileName(String)} or
+ * {@link org.apache.wicket.request.resource.AbstractResource.ResourceResponse#setFileName(String)}</p>
+ * 
+ * @author svenmeier
+ * @author Martin Grigorov
+ * @author Maxim Solodovnik
+ */
+public class AjaxDownloadBehavior extends AbstractDefaultAjaxBehavior
+{
+	private static final long serialVersionUID = 1L;
+
+	public enum Location {
+		/**
+		 * The resource will be downloaded into a {@code blob}.
+		 * <p>
+		 * This is recommended for modern browsers.
+		 */
+		Blob,
+
+		/**
+		 * The resource will be downloaded via a temporary created iframe, the resource has to be a
+		 * {@link ContentDisposition#ATTACHMENT}.
+		 * <p>
+		 * This is recommended when there are resources in the DOM which will be
+		 * closed automatically on JavaScript <em>unload</em> event, like WebSockets.
+		 * Supports both <em>success</em> and <em>failure</em> callbacks!
+		 */
+		IFrame,
+
+		/**
+		 * The resource will be downloaded by changing the location of the current DOM document,
+		 * the resource has to be a {@link ContentDisposition#ATTACHMENT}.
+		 * <p>
+		 * Note: This will trigger JavaScript <em>unload</em> event on the page!
+		 * Does not support {@link AjaxDownloadBehavior#onDownloadFailed(AjaxRequestTarget)} callback,
+		 * i.e. it is not possible to detect when the download has failed!
+		 */
+		SameWindow,
+
+		/**
+		 * The resource will be downloaded in a new browser window by using JavaScript <code>window.open()</code> API,
+		 * the resource has to be a {@link ContentDisposition#INLINE}.
+		 */
+		NewWindow
+	}
+
+	/**
+	 * 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(
+		AjaxDownloadBehavior.class, "wicket-ajaxdownload.js");
+
+	private final ResourceReference resourceReference;
+
+	private final ResourceBehavior resourceBehavior;
+
+	private PageParameters resourceParameters;
+
+	private Location location = Location.Blob;
+
+	/**
+	 * Download of a {@link Resource}.
+	 * 
+	 * @param resource
+	 *            resource to download
+	 */
+	public AjaxDownloadBehavior(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 AjaxDownloadBehavior(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 AjaxDownloadBehavior(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("downloadUrl", url);
+		settings.put("method", getLocation().name().toLowerCase(Locale.ENGLISH));
+
+		target.appendJavaScript(String.format("Wicket.AjaxDownload.initiate(%s);", settings));
+
+		onBeforeDownload(target);
+	}
+
+	protected void onBeforeDownload(AjaxRequestTarget target)
+	{
+	}
+
+	/**
+	 * A callback executed when the download of the resource finished successfully.
+	 *
+	 * @param target The Ajax request handler
+	 */
+	protected void onDownloadSuccess(AjaxRequestTarget target)
+	{
+	}
+
+	/**
+	 * A callback executed when the download of the resource failed for some reason,
+	 * e.g. an error at the server side.
+	 * <p>
+	 * Since the HTTP status code of the download is not available to Wicket, any HTML in the resource response
+	 * will be interpreted as a failure HTTP status message. Thus is it not possible to download HTML resources
+	 * via {@link AjaxDownloadBehavior}.
+	 *
+	 * @param target The Ajax request handler
+	 */
+	protected void onDownloadFailed(AjaxRequestTarget target)
+	{
+	}
+
+	/**
+	 * A callback executed when the download of the resource finished successfully or with a failure.
+	 *
+	 * @param target The Ajax request handler
+	 */
+	protected void onDownloadCompleted(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);
+		}
+		onDownloadCompleted(target);
+	}
+
+	public Location getLocation() {
+		return location;
+	}
+
+	public AjaxDownloadBehavior setLocation(final Location location) {
+		this.location = Args.notNull(location, "location");
+		return this;
+	}
+
+	/**
+	 * 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 IRequestListener
+	{
+		private final IResource resource;
+
+		private ResourceBehavior(IResource resource)
+		{
+			this.resource = Args.notNull(resource, "resource");
+		}
+
+		@Override
+		public boolean rendersPage()
+		{
+			return false;
+		}
+		
+		@Override
+		public void onRequest()
+		{
+			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().urlForListener(this, null);
+		}
+	}
+
+	/**
+	 * Mark a resource as complete.
+	 * <p>
+	 * Has to be called from {@link IResource#respond(Attributes)} when downloaded via
+	 * {@link #AjaxDownloadBehavior(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");
+		
+		// has to be on root, otherwise JavaScript will not be able to access the
+		// cookie when it is set from a different path - which is the case when a
+		// ResourceReference is used
+		cookie.setPath("/");
+		
+		return cookie;
+	}
+}