You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by kn...@apache.org on 2008/08/24 14:49:43 UTC

svn commit: r688502 [2/2] - in /wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajaxng: ./ js/ json/ request/

Modified: wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajaxng/request/AjaxRequestTarget.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajaxng/request/AjaxRequestTarget.java?rev=688502&r1=688501&r2=688502&view=diff
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajaxng/request/AjaxRequestTarget.java (original)
+++ wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajaxng/request/AjaxRequestTarget.java Sun Aug 24 05:49:42 2008
@@ -16,22 +16,137 @@
  */
 package org.apache.wicket.ajaxng.request;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.Application;
 import org.apache.wicket.Component;
 import org.apache.wicket.IRequestTarget;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
 import org.apache.wicket.RequestCycle;
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.Response;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajaxng.AjaxBehavior;
+import org.apache.wicket.ajaxng.json.JSONArray;
+import org.apache.wicket.ajaxng.json.JSONObject;
+import org.apache.wicket.behavior.IBehavior;
+import org.apache.wicket.markup.html.IHeaderResponse;
+import org.apache.wicket.markup.html.internal.HeaderResponse;
+import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
+import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
+import org.apache.wicket.markup.repeater.AbstractRepeater;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.response.StringResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class AjaxRequestTarget implements IRequestTarget
 {
 
 	private AjaxRequestTarget()
 	{
+		this.component = null;
+		this.page = null;
+		this.behaviorIndex = -1;
+	}
+
+	/**
+	 * An {@link AjaxRequestTarget} listener that can be used to respond to various target-related
+	 * events
+	 * 
+	 */
+	public static interface IListener
+	{
+		/**
+		 * Triggered before ajax request target begins its response cycle
+		 * 
+		 * @param components
+		 *            read-only list of component entries already added to the target
+		 * @param target
+		 *            the target itself. Could be used to add components or to append/prepend
+		 *            javascript
+		 * 
+		 */
+		public void onBeforeRespond(List<ComponentEntry> components, AjaxRequestTarget target);
+
+		/**
+		 * Triggered after ajax request target is done with its response cycle. At this point only
+		 * additional javascript can be output to the response using the provided
+		 * {@link IJavascriptResponse} object
+		 * 
+		 * NOTE: During this stage of processing any calls to target that manipulate the response
+		 * (adding components, javascript) will have no effect
+		 * 
+		 * @param components
+		 *            read-only list of component entries added to the target
+		 * @param response
+		 *            response object that can be used to output javascript
+		 */
+		public void onAfterRespond(List<ComponentEntry> components, IJavascriptResponse response);
 	}
-	
-	private Component component;
-	private int behaviorIndex;
-	
+
+	/**
+	 * An ajax javascript response that allows users to add javascript to be executed on the client
+	 * side
+	 * 
+	 * @author ivaynberg
+	 */
+	public static interface IJavascriptResponse
+	{
+		/**
+		 * Adds more javascript to the ajax response that will be executed on the client side
+		 * 
+		 * @param script
+		 *            javascript
+		 */
+		public void addJavascript(String script);
+	}
+
+	private final Page page;
+	private final Component component;
+	private final int behaviorIndex;
+
+	private final List<ComponentEntry> entries = new ArrayList<ComponentEntry>();
+
+	private final List<JavascriptEntry> prependJavascripts = new ArrayList<JavascriptEntry>();
+	private final List<JavascriptEntry> appendJavascripts = new ArrayList<JavascriptEntry>();
+
+	private final List<JavascriptEntry> domReadyJavascripts = new ArrayList<JavascriptEntry>();
+
+	private final List<IListener> listeners = new ArrayList<IListener>();
+
+	private static final Logger log = LoggerFactory.getLogger(AjaxRequestTarget.class);
+
+	private String redirect = null;
+
+	// whether a header contribution is being rendered
+	private boolean headerRendering = false;
+	private HtmlHeaderContainer header = null;
+
+	private IHeaderResponse headerResponse;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param component
+	 * @param behaviorIndex
+	 */
 	public AjaxRequestTarget(Component component, int behaviorIndex)
 	{
+		if (component == null)
+		{
+			throw new IllegalArgumentException("Argument 'component' may not be null.");
+		}
+		page = component.getPage();
+		if (page == null)
+		{
+			throw new IllegalArgumentException("Component must belong to a page.");
+		}
 		this.component = component;
 		this.behaviorIndex = behaviorIndex;
 	}
@@ -40,8 +155,854 @@
 	{
 	}
 
+	/**
+	 * Returns component that has behavior which initiated this Ajax request.
+	 * 
+	 * @return component
+	 */
+	public Component getComponent()
+	{
+		return component;
+	}
+
+	/**
+	 * Entry for a single component. Allows to specify custom javascript handlers executed before
+	 * and after replacing the component. Also the actual component replacement can be overriden.
+	 * 
+	 * @author Matej Knopp
+	 */
+	public static class ComponentEntry
+	{
+		private final Component component;
+		private String beforeReplaceJavascript;
+		private String afterReplaceJavascript;
+		private String replaceJavascript;
+
+		/**
+		 * Construct.
+		 * 
+		 * @param component
+		 */
+		public ComponentEntry(Component component)
+		{
+			this.component = component;
+		}
+
+		/**
+		 * Construct.
+		 * 
+		 * @param entry
+		 *            entry to copy
+		 */
+		public ComponentEntry(ComponentEntry entry)
+		{
+			this.component = entry.component;
+			this.beforeReplaceJavascript = entry.beforeReplaceJavascript;
+			this.afterReplaceJavascript = entry.afterReplaceJavascript;
+			this.replaceJavascript = entry.replaceJavascript;
+		}
+
+		/**
+		 * Returns component that will be updated.
+		 * 
+		 * @return component
+		 */
+		public Component getComponent()
+		{
+			return component;
+		}
+
+		/**
+		 * Sets the javascript executed right before replacing the component.
+		 * <p>
+		 * The javascript can use following variables:
+		 * <dl>
+		 * <dt>requestQueueItem</dt>
+		 * <dd>RequestQueueItem instance for current request</dd>
+		 * <dt>componentId</dt>
+		 * <dd>MarkupId of component that is about to be replaced
+		 * <dt>sourceComponentId</dt>
+		 * <dd>MarkupId of component that has initiated current ajax request or <code>null</code>
+		 * if the component is not available.
+		 * <dt>done</dt>
+		 * <dd>Method that javascript needs to execute after it has finished. Note that it is
+		 * mandatory to call this method otherwise the processing pipeline will stop</dd>
+		 * </dl>
+		 * 
+		 * @param beforeReplaceJavascript
+		 *            the javascript
+		 */
+		public void setBeforeReplaceJavascript(String beforeReplaceJavascript)
+		{
+			this.beforeReplaceJavascript = beforeReplaceJavascript;
+		}
+
+		/**
+		 * Returns the javascript executed before replacing the component.
+		 * 
+		 * @see #setBeforeReplaceJavascript(String)
+		 * @return javascript
+		 */
+		public String getBeforeReplaceJavascript()
+		{
+			return beforeReplaceJavascript;
+		}
+
+		/**
+		 * Sets the javascript executed right after replacing the component.
+		 * <p>
+		 * The javascript can use following variables:
+		 * <dl>
+		 * <dt>requestQueueItem</dt>
+		 * <dd>RequestQueueItem instance for current request</dd>
+		 * <dt>componentId</dt>
+		 * <dd>MarkupId of component that has been replaced
+		 * <dt>done</dt>
+		 * <dd>Method that javascript needs to execute after it has finished. Note that it is
+		 * mandatory to call this method otherwise the processing pipeline will stop</dd>
+		 * </dl>
+		 * 
+		 * @param afterReplaceJavascript
+		 *            the javascript
+		 */
+		public void setAfterReplaceJavascript(String afterReplaceJavascript)
+		{
+			this.afterReplaceJavascript = afterReplaceJavascript;
+		}
+
+		/**
+		 * Returns the javascript executed after replacing the component.
+		 * 
+		 * @see #setAfterReplaceJavascript(String)
+		 * @return javascript
+		 */
+		public String getAfterReplaceJavascript()
+		{
+			return afterReplaceJavascript;
+		}
+
+		/**
+		 * Sets the javascript executed to replace the component.
+		 * <p>
+		 * The javascript can use following variables:
+		 * <dl>
+		 * <dt>requestQueueItem</dt>
+		 * <dd>RequestQueueItem instance for current request</dd>
+		 * <dt>componentId</dt>
+		 * <dd>MarkupId of component that has been replaced
+		 * <dt>markup</dt>
+		 * <dd>The new markup that should replace current markup</dd>
+		 * <dt>done</dt>
+		 * <dd>Method that javascript needs to execute after the component has been replaced. Note
+		 * that it is mandatory to call this method otherwise the processing pipeline will stop</dd>
+		 * </dl>
+		 * 
+		 * An example javascript:
+		 * 
+		 * <pre>
+		 * var element = W.$(componentId);
+		 * W.replaceOuterHtml(element, markup);
+		 * done();
+		 * </pre>
+		 * 
+		 * @param replaceJavascript
+		 *            the javascript
+		 */
+		public void setReplaceJavascript(String replaceJavascript)
+		{
+			this.replaceJavascript = replaceJavascript;
+		}
+
+		/**
+		 * Returns the javascript executed to replace component.
+		 * 
+		 * @see #setReplaceJavascript(String)
+		 * @return javsacript executed to replace component
+		 */
+		public String getReplaceJavascript()
+		{
+			return replaceJavascript;
+		}
+	}
+
+	private static class UnmodifiableComponentEntry extends ComponentEntry
+	{
+
+		public UnmodifiableComponentEntry(ComponentEntry entry)
+		{
+			super(entry);
+		}
+
+		@Override
+		public void setAfterReplaceJavascript(String javascript)
+		{
+			throw new UnsupportedOperationException("ComponentEntry is can not be modified.");
+		}
+
+		@Override
+		public void setBeforeReplaceJavascript(String javascript)
+		{
+			throw new UnsupportedOperationException("ComponentEntry is can not be modified.");
+		}
+
+		@Override
+		public void setReplaceJavascript(String javascript)
+		{
+			throw new UnsupportedOperationException("ComponentEntry is can not be modified.");
+		}
+	}
+
+	private boolean isParent(Component parent, Component component)
+	{
+		Component p = component.getParent();
+		while (p != null && p != parent)
+		{
+			p = p.getParent();
+		}
+		return p == null;
+	}
+
+	private void checkComponent(Component component)
+	{
+		if (component == null)
+		{
+			throw new IllegalArgumentException("Component may not be null.");
+		}
+		else if (component instanceof Page)
+		{
+			throw new IllegalArgumentException("Component cannot be a page");
+		}
+		else if (!component.getOutputMarkupId())
+		{
+			throw new IllegalStateException("Component " + component.getClass().getName() +
+				" must have setOuputMarkupId set in order to be updated via ajax.");
+		}
+		else if (component.getRenderBodyOnly())
+		{
+			throw new IllegalStateException("Component " + component.getClass().getName() +
+				" must not have setRenderBodyOnly set in order to be updated via ajax.");
+		}
+		else if (component instanceof AbstractRepeater)
+		{
+			throw new IllegalArgumentException(
+				"Component " +
+					component.getClass().getName() +
+					" has been added to the target. This component is a repeater and cannot be repainted via ajax directly. Instead add its parent or another markup container higher in the hierarchy.");
+		}
+	}
+
+	public boolean addComponent(ComponentEntry entry)
+	{
+		if (entry == null)
+		{
+			throw new IllegalArgumentException("Argument 'entry' may not be null.");
+		}
+
+		final Component component = entry.getComponent();
+		checkComponent(component);
+
+		for (ComponentEntry e : entries)
+		{
+			if (e.getComponent() == component)
+			{
+				return false;
+			}
+			// check if component's parent is already in queue
+			else if (isParent(e.getComponent(), component))
+			{
+				return false;
+			}
+			// check if new component is parent of existing component
+			else if (isParent(component, e.getComponent()))
+			{
+				entries.remove(e);
+				break;
+			}
+		}
+		entries.add(entry);
+		return true;
+	}
+
+	public boolean addComponent(Component component)
+	{
+		if (component == null)
+		{
+			throw new IllegalArgumentException("Argument 'component' may not be null.");
+		}
+		return addComponent(new ComponentEntry(component));
+	}
+
+	private static class JavascriptEntry
+	{
+		private final String javascript;
+		private final boolean async;
+
+		public JavascriptEntry(String javascript, boolean async)
+		{
+			this.javascript = javascript;
+			this.async = async;
+		}
+
+		public String getJavascript()
+		{
+			return javascript;
+		}
+
+		public boolean isAsync()
+		{
+			return async;
+		}
+	};
+
+	/**
+	 * Adds javascript that will be evaluated on the client side before components are replaced
+	 * <p>
+	 * The javascript can use following variables:
+	 * <dl>
+	 * <dt>requestQueueItem</dt>
+	 * <dd>RequestQueueItem instance for current request</dd>
+	 * <dt>done</dt>
+	 * <dd>Must be called for asynchronous javascript
+	 * </dl>
+	 * 
+	 * @param javascript
+	 *            javascript to be evaluated
+	 * @param async
+	 *            indicates if the javascript should be evaluated asynchrously. If
+	 *            <code>async</code> is <code>true</code>, the javascript must invoke the
+	 *            <code>done</code> function that it gets passed for the processing queue to
+	 *            continue.
+	 */
+	public void prependJavascript(String javascript, boolean async)
+	{
+		if (javascript == null)
+		{
+			throw new IllegalArgumentException("Argument 'javascript' may not be null.");
+		}
+		prependJavascripts.add(new JavascriptEntry(javascript, async));
+	}
+
+	/**
+	 * Adds javascript that will be evaluated on the client side before components are replaced. The
+	 * javascript will be executed synchronously which means that the processing queue will be held
+	 * until the javascript finishes.
+	 * <p>
+	 * The javascript can use following variables:
+	 * <dl>
+	 * <dt>requestQueueItem</dt>
+	 * <dd>RequestQueueItem instance for current request</dd>
+	 * </dl>
+	 * 
+	 * @param javascript
+	 *            javascript to be evaluated
+	 */
+	public void prependJavascript(String javascript)
+	{
+		prependJavascript(javascript, false);
+	}
+
+	/**
+	 * Adds javascript that will be evaluated on the client side after components are replaced
+	 * <p>
+	 * The javascript can use following variables:
+	 * <dl>
+	 * <dt>requestQueueItem</dt>
+	 * <dd>RequestQueueItem instance for current request</dd>
+	 * <dt>done</dt>
+	 * <dd>Must be called for asynchronous javascript
+	 * </dl>
+	 * 
+	 * @param javascript
+	 *            javascript to be evaluated
+	 * @param async
+	 *            indicates if the javascript should be evaluated asynchrously. If
+	 *            <code>async</code> is <code>true</code>, the javascript must invoke the
+	 *            <code>done</code> function that it gets passed for the processing queue to
+	 *            continue.
+	 */
+	public void appendJavascript(String javascript, boolean async)
+	{
+		if (javascript == null)
+		{
+			throw new IllegalArgumentException("Argument 'javascript' may not be null.");
+		}
+		appendJavascripts.add(new JavascriptEntry(javascript, async));
+	}
+
+	/**
+	 * Adds javascript that will be evaluated on the client side after components are replaced. The
+	 * javascript will be executed synchronously which means that the processing queue will be held
+	 * until the javascript finishes.
+	 * <p>
+	 * The javascript can use following variables:
+	 * <dl>
+	 * <dt>requestQueueItem</dt>
+	 * <dd>RequestQueueItem instance for current request</dd>
+	 * </dl>
+	 * 
+	 * @param javascript
+	 *            javascript to be evaluated
+	 */
+	public void appendJavascript(String javascript)
+	{
+		appendJavascript(javascript, false);
+	}
+
+
+	/**
+	 * Adds a listener to this target
+	 * 
+	 * @param listener
+	 */
+	public void addListener(IListener listener)
+	{
+		if (listener == null)
+		{
+			throw new IllegalArgumentException("Argument `listener` cannot be null");
+		}
+		listeners.add(listener);
+	}
+
+	private List<ComponentEntry> entriesCopy()
+	{
+		List<ComponentEntry> list = new ArrayList<ComponentEntry>(entries.size());
+		for (ComponentEntry e : entries)
+		{
+			list.add(new UnmodifiableComponentEntry(e));
+		}
+		return Collections.unmodifiableList(list);
+	}
+
+	private void fireOnBeforeRespondListeners(List<ComponentEntry> entries)
+	{
+		if (!listeners.isEmpty())
+		{
+			for (IListener l : listeners)
+			{
+				l.onBeforeRespond(entries, this);
+			}
+		}
+	}
+
+	private void fireOnAfterRespondListeners(List<ComponentEntry> entries)
+	{
+		// invoke onafterresponse event on listeners
+		if (!(listeners.isEmpty()))
+		{
+			// create response that will be used by listeners to append
+			// javascript
+			final IJavascriptResponse jsresponse = new IJavascriptResponse()
+			{
+
+				public void addJavascript(String script)
+				{
+					appendJavascript(script, false);
+				}
+			};
+
+			for (IListener listener : listeners)
+			{
+				listener.onAfterRespond(entries, jsresponse);
+			}
+		}
+	}
+
+	/**
+	 * Header response for an ajax request.
+	 * 
+	 * @author Matej Knopp
+	 */
+	private class AjaxHeaderResponse extends HeaderResponse
+	{
+
+		private static final long serialVersionUID = 1L;
+
+		private void checkHeaderRendering()
+		{
+			if (headerRendering == false)
+			{
+				throw new WicketRuntimeException(
+					"Only methods that can be called on IHeaderResponse outside renderHead() are renderOnLoadJavascript and renderOnDomReadyJavascript");
+			}
+		}
+
+		@Override
+		public void renderCSSReference(ResourceReference reference, String media)
+		{
+			checkHeaderRendering();
+			super.renderCSSReference(reference, media);
+		}
+
+		@Override
+		public void renderCSSReference(String url)
+		{
+			checkHeaderRendering();
+			super.renderCSSReference(url);
+		}
+
+		@Override
+		public void renderCSSReference(String url, String media)
+		{
+			checkHeaderRendering();
+			super.renderCSSReference(url, media);
+		}
+
+		@Override
+		public void renderJavascript(CharSequence javascript, String id)
+		{
+			checkHeaderRendering();
+			super.renderJavascript(javascript, id);
+		}
+
+		@Override
+		public void renderCSSReference(ResourceReference reference)
+		{
+			checkHeaderRendering();
+			super.renderCSSReference(reference);
+		}
+
+		@Override
+		public void renderJavascriptReference(ResourceReference reference)
+		{
+			checkHeaderRendering();
+			super.renderJavascriptReference(reference);
+		}
+
+		@Override
+		public void renderJavascriptReference(ResourceReference reference, String id)
+		{
+			checkHeaderRendering();
+			super.renderJavascriptReference(reference, id);
+		}
+
+		@Override
+		public void renderJavascriptReference(String url)
+		{
+			checkHeaderRendering();
+			super.renderJavascriptReference(url);
+		}
+
+		@Override
+		public void renderJavascriptReference(String url, String id)
+		{
+			checkHeaderRendering();
+			super.renderJavascriptReference(url, id);
+		}
+
+		@Override
+		public void renderString(CharSequence string)
+		{
+			checkHeaderRendering();
+			super.renderString(string);
+		}
+
+		/**
+		 * Construct.
+		 */
+		public AjaxHeaderResponse()
+		{
+
+		}
+
+		/**
+		 * 
+		 * @see org.apache.wicket.markup.html.internal.HeaderResponse#renderOnDomReadyJavascript(java.lang.String)
+		 */
+		@Override
+		public void renderOnDomReadyJavascript(String javascript)
+		{
+			List<String> token = Arrays.asList(new String[] { "javascript-event", "window",
+					"domready", javascript });
+			if (wasRendered(token) == false)
+			{
+				domReadyJavascripts.add(new JavascriptEntry(javascript, false));
+				markRendered(token);
+			}
+		}
+
+		/**
+		 * 
+		 * @see org.apache.wicket.markup.html.internal.HeaderResponse#renderOnLoadJavascript(java.lang.String)
+		 */
+		@Override
+		public void renderOnLoadJavascript(String javascript)
+		{
+			List<String> token = Arrays.asList(new String[] { "javascript-event", "window", "load",
+					javascript });
+			if (wasRendered(token) == false)
+			{
+				// execute the javascript after all other scripts are executed
+				appendJavascript(javascript, false);
+				markRendered(token);
+			}
+		}
+
+		/**
+		 * 
+		 * @see org.apache.wicket.markup.html.internal.HeaderResponse#getRealResponse()
+		 */
+		@Override
+		protected Response getRealResponse()
+		{
+			return RequestCycle.get().getResponse();
+		}
+	};
+
+	/**
+	 * Returns the header response associated with current AjaxRequestTarget.
+	 * 
+	 * Beware that only renderOnDomReadyJavascript and renderOnLoadJavascript can be called outside
+	 * the renderHeader(IHeaderResponse response) method. Calls to other render** methods will
+	 * result in an exception being thrown.
+	 * 
+	 * @return header response
+	 */
+	public IHeaderResponse getHeaderResponse()
+	{
+		if (headerResponse == null)
+		{
+			headerResponse = new AjaxHeaderResponse();
+		}
+		return headerResponse;
+	}
+
+	/**
+	 * Header container component for ajax header contributions
+	 * 
+	 * @author Matej Knopp
+	 */
+	private static class AjaxHtmlHeaderContainer extends HtmlHeaderContainer
+	{
+		private static final long serialVersionUID = 1L;
+
+		/**
+		 * Construct.
+		 * 
+		 * @param id
+		 * @param target
+		 */
+		public AjaxHtmlHeaderContainer(String id, AjaxRequestTarget target)
+		{
+			super(id);
+			this.target = target;
+		}
+
+		/**
+		 * 
+		 * @see org.apache.wicket.markup.html.internal.HtmlHeaderContainer#newHeaderResponse()
+		 */
+		@Override
+		protected IHeaderResponse newHeaderResponse()
+		{
+			return target.getHeaderResponse();
+		}
+
+		private final transient AjaxRequestTarget target;
+	};
+
+	/**
+	 * 
+	 * @param response
+	 * @param component
+	 */
+	private void respondHeaderContribution(final Response response, final Component component)
+	{
+		// render the head of component and all it's children
+
+		component.renderHead(header);
+
+		if (component instanceof MarkupContainer)
+		{
+			((MarkupContainer)component).visitChildren(new Component.IVisitor<Component>()
+			{
+				public Object component(Component component)
+				{
+					if (component.isVisible())
+					{
+						component.renderHead(header);
+						return CONTINUE_TRAVERSAL;
+					}
+					else
+					{
+						return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
+					}
+				}
+			});
+		}
+	}
+
+	private String respondHeaderContribution()
+	{
+		headerRendering = true;
+
+		// create the htmlheadercontainer if needed
+		if (header == null)
+		{
+			header = new AjaxHtmlHeaderContainer(HtmlHeaderSectionHandler.HEADER_ID, this);
+			final Page page = component.getPage();
+			page.addOrReplace(header);
+		}
+
+		// save old response, set new
+		StringResponse stringResponse = new StringResponse();
+		Response oldResponse = RequestCycle.get().setResponse(stringResponse);
+
+		for (ComponentEntry e : entries)
+		{
+			respondHeaderContribution(stringResponse, component);
+		}
+
+		// revert to old response
+		RequestCycle.get().setResponse(oldResponse);
+
+		headerRendering = false;
+		return stringResponse.toString();
+	}
+
+	public void setRedirect(String redirect)
+	{
+		this.redirect = redirect;
+	}
+
+	private void prepareRender()
+	{
+		for (Iterator<ComponentEntry> i = entries.iterator(); i.hasNext();)
+		{
+			ComponentEntry entry = i.next();
+			Component component = entry.getComponent();
+
+			final Page page = (Page)component.findParent(Page.class);
+			if (page == null)
+			{
+				// dont throw an exception but just ignore this component, somehow
+				// it got removed from the page.
+				log.debug("component: " + component + " with markupid: " + component.getMarkupId() +
+					" not rendered because it was already removed from page");
+				i.remove();
+				continue;
+			}
+
+			checkComponent(component);
+
+			component.prepareForRender();
+		}
+	}
+
+	private String renderComponent(Component component)
+	{
+		StringResponse stringResponse = new StringResponse();
+		Response originalResponse = RequestCycle.get().setResponse(stringResponse);
+
+		page.startComponentRender(component);
+		component.renderComponent();
+		page.endComponentRender(component);
+
+		RequestCycle.get().setResponse(originalResponse);
+		return stringResponse.toString();
+	}
+
+	private JSONObject renderComponentEntry(ComponentEntry componentEntry)
+	{
+		JSONObject object = new JSONObject();
+
+		Component component = componentEntry.getComponent();
+		object.put("componentId", component.getId());
+		object.put("beforeReplaceJavascript", componentEntry.getBeforeReplaceJavascript());
+		object.put("afterReplaceJavascript", componentEntry.getAfterReplaceJavascript());
+		object.put("replaceJavascript", componentEntry.getReplaceJavascript());
+		object.put("markup", renderComponent(component));
+
+		return object;
+	}
+
+	private JSONObject renderJavascriptEntry(JavascriptEntry javascriptEntry)
+	{
+		JSONObject object = new JSONObject();
+
+		object.put("async", javascriptEntry.isAsync());
+		object.put("javascript", javascriptEntry.getJavascript());
+
+		return object;
+	}
+
 	public void respond(RequestCycle requestCycle)
 	{
+		IBehavior behavior = component.getBehaviors().get(behaviorIndex);
+		if (behavior instanceof AjaxBehavior == false)
+		{
+			throw new WicketRuntimeException("Behavior must be instance of AjaxBehavior.");
+		}
+		((AjaxBehavior)behavior).respond(this);
+
+		List<ComponentEntry> entriesCopy = entriesCopy();
+		fireOnBeforeRespondListeners(entriesCopy);
+
+		JSONObject response = new JSONObject();
+
+		if (redirect != null)
+		{
+			response.put("redirect", redirect);
+		}
+		else
+		{
+			prepareRender();
+
+			response.put("header", respondHeaderContribution());
+
+			JSONArray components = new JSONArray();
+			response.put("components", components);
+			for (ComponentEntry entry : entries)
+			{
+				components.put(renderComponentEntry(entry));
+			}
+
+			fireOnAfterRespondListeners(entries);
+
+			JSONArray prependJavascripts = new JSONArray();
+			response.put("prependJavascript", prependJavascripts);
+
+			for (JavascriptEntry e : this.prependJavascripts)
+			{
+				prependJavascripts.put(renderJavascriptEntry(e));
+			}
+
+			JSONArray appendJavascripts = new JSONArray();
+			response.put("appendJavascript", appendJavascripts);
+
+			for (JavascriptEntry e : this.domReadyJavascripts)
+			{
+				appendJavascripts.put(renderJavascriptEntry(e));
+			}
+
+			for (JavascriptEntry e : this.appendJavascripts)
+			{
+				appendJavascripts.put(renderJavascriptEntry(e));
+			}
+		}
+		
+		WebResponse webResponse = (WebResponse) requestCycle.getResponse();
+		prepareResponse(webResponse);
+		
+		webResponse.write("if (false) (");
+		webResponse.write(response.toString());
+		webResponse.write(")");
+	}
+
+	private void prepareResponse(WebResponse response)
+	{
+		final Application app = Application.get();
+		
+		// Determine encoding
+		final String encoding = app.getRequestCycleSettings().getResponseRequestEncoding();
+
+		// Set content type based on markup type for page
+		response.setCharacterEncoding(encoding);
+		response.setContentType("text/xml; charset=" + encoding);
+
+		// Make sure it is not cached by a client
+		response.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
+		response.setHeader("Cache-Control", "no-cache, must-revalidate");
+		response.setHeader("Pragma", "no-cache");
 	}
 
 	public static final AjaxRequestTarget DUMMY = new AjaxRequestTarget();