You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by ad...@apache.org on 2016/06/19 14:08:03 UTC

wicket git commit: WICKET-6183 Improve stateless support for AJAX

Repository: wicket
Updated Branches:
  refs/heads/wicket-7.x 764ba8a58 -> b7fb96171


WICKET-6183 Improve stateless support for AJAX


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

Branch: refs/heads/wicket-7.x
Commit: b7fb96171990a455cc238c356f23853a29331b47
Parents: 764ba8a
Author: Andrea Del Bene <an...@innoteam.it>
Authored: Tue Apr 5 18:43:38 2016 +0200
Committer: Andrea Del Bene <ad...@apache.org>
Committed: Sun Jun 19 16:06:50 2016 +0200

----------------------------------------------------------------------
 .../ajax/AbstractDefaultAjaxBehavior.java       |  10 +-
 .../ajax/form/AjaxFormSubmitBehavior.java       |   2 +-
 .../ajax/markup/html/AjaxFallbackLink.java      |   7 +
 .../wicket/ajax/markup/html/AjaxLink.java       |  12 +
 .../ajax/markup/html/form/AjaxButton.java       |  13 ++
 .../ajax/markup/html/form/AjaxSubmitLink.java   |  14 ++
 .../org/apache/wicket/ajax/StatelessPage.html   |  40 ++++
 .../org/apache/wicket/ajax/StatelessPage.java   | 170 ++++++++++++++
 .../html/StatelessAjaxFallbackLinkTest.java     |  70 ++++++
 .../html/form/StatelessAjaxSubmitLinkTest.java  |  71 ++++++
 .../stateless/AjaxStatelessExample.html         |  72 ++++++
 .../stateless/AjaxStatelessExample.java         | 220 +++++++++++++++++++
 .../apache/wicket/examples/stateless/Index.html |   3 +
 .../apache/wicket/examples/stateless/Index.java |  13 ++
 .../src/docs/guide/ajax/ajax_7.gdoc             |  43 +++-
 .../src/docs/guide/ajax/ajax_8.gdoc             |   7 +
 wicket-user-guide/src/docs/guide/toc.yml        |   3 +-
 17 files changed, 764 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
index 43f031d..b95d24c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
@@ -81,7 +81,15 @@ public abstract class AbstractDefaultAjaxBehavior extends AbstractAjaxBehavior
 	@Override
 	protected void onBind()
 	{
-		getComponent().setOutputMarkupId(true);
+		final Component component = getComponent();
+		
+		component.setOutputMarkupId(true);
+		
+		if (getStatelessHint(component))
+		{
+			//generate behavior id
+			component.getBehaviorId(this);
+		}
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java
index c734e9b..69ff89a 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormSubmitBehavior.java
@@ -168,7 +168,7 @@ public abstract class AjaxFormSubmitBehavior extends AjaxEventBehavior
 	@Override
 	protected void onEvent(final AjaxRequestTarget target)
 	{
-		getForm().getRootForm().onFormSubmitted(new AjaxFormSubmitter(this, target));
+		getForm().getRootForm().onFormSubmitted(new AjaxFormSubmitBehavior.AjaxFormSubmitter(this, target));
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxFallbackLink.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxFallbackLink.java b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxFallbackLink.java
index 18488da..6c5f348 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxFallbackLink.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxFallbackLink.java
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket.ajax.markup.html;
 
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxEventBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
@@ -97,6 +98,12 @@ public abstract class AjaxFallbackLink<T> extends Link<T> implements IAjaxLink
 				attributes.setPreventDefault(true);
 				AjaxFallbackLink.this.updateAjaxAttributes(attributes);
 			}
+			
+			@Override
+			public boolean getStatelessHint(Component component)
+			{				
+				return AjaxFallbackLink.this.getStatelessHint();
+			}
 		};
 	}
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxLink.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxLink.java b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxLink.java
index 359edd5..825c0f2 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxLink.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/AjaxLink.java
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket.ajax.markup.html;
 
+import org.apache.wicket.Component;
 import org.apache.wicket.IGenericComponent;
 import org.apache.wicket.ajax.AjaxEventBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -90,6 +91,12 @@ public abstract class AjaxLink<T> extends AbstractLink implements IAjaxLink, IGe
 				super.updateAjaxAttributes(attributes);
 				AjaxLink.this.updateAjaxAttributes(attributes);
 			}
+			
+			@Override
+			public boolean getStatelessHint(Component component)
+			{
+				return AjaxLink.this.getStatelessHint();
+			}
 		};
 	}
 
@@ -159,4 +166,9 @@ public abstract class AjaxLink<T> extends AbstractLink implements IAjaxLink, IGe
 		setDefaultModelObject(object);
 	}
 
+	@Override
+	protected boolean getStatelessHint()
+	{
+		return false;
+	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxButton.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxButton.java b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxButton.java
index 8706414..9a03463 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxButton.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxButton.java
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket.ajax.markup.html.form;
 
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
@@ -140,6 +141,12 @@ public abstract class AjaxButton extends Button
 			{
 				return AjaxButton.this.getDefaultFormProcessing();
 			}
+			
+			@Override
+			public boolean getStatelessHint(Component component)
+			{
+				return AjaxButton.this.getStatelessHint();
+			}
 		};
 	}
 
@@ -223,4 +230,10 @@ public abstract class AjaxButton extends Button
 	protected void onError(AjaxRequestTarget target, Form<?> form)
 	{
 	}
+	
+	@Override
+	protected boolean getStatelessHint()
+	{
+		return false;
+	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxSubmitLink.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxSubmitLink.java b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxSubmitLink.java
index 619e289..f032552 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxSubmitLink.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/markup/html/form/AjaxSubmitLink.java
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket.ajax.markup.html.form;
 
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
@@ -115,6 +116,12 @@ public abstract class AjaxSubmitLink extends AbstractSubmitLink
 			{
 				AjaxSubmitLink.this.onAfterSubmit(target, getForm());
 			}
+			
+			@Override
+			public boolean getStatelessHint(Component component)
+			{
+				return AjaxSubmitLink.this.getStatelessHint();
+			}
 		};
 	}
 
@@ -212,4 +219,11 @@ public abstract class AjaxSubmitLink extends AbstractSubmitLink
 	{
 		logger.warn("unexpected invocation of #onAfterSubmit() on {}", this);
 	}
+	
+	
+	@Override
+	protected boolean getStatelessHint()
+	{
+		return false;
+	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.html b/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.html
new file mode 100644
index 0000000..2ab9df2
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.html
@@ -0,0 +1,40 @@
+<!--
+
+     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.
+
+-->
+<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
+    <head>  
+        <title>Wicket Quickstart Archetype Homepage</title>
+    </head>
+    <body>
+      <ul wicket:id="list">
+        <li wicket:id="item"><span wicket:id="value">Item 1</span></li>
+      </ul>
+      <div id="new"></div>
+      <a href="#" wicket:id="more">More Data</a>
+      <a href="#" wicket:id="home">Home</a>
+      
+      <form wicket:id="inputForm" >
+		<label for="name">Name:</label>
+		<input wicket:id="name" id="name" type="text" size="15"/><br/>
+		<label for="surname">Surname:</label>
+		<input wicket:id="surname" id="surname" type="text" size="15"/><br/>
+		<button id="submitButton" wicket:id="submit">Submit</button>
+		<div wicket:id="feedback" id="feedback"></div>
+	  </form>	
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.java b/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.java
new file mode 100644
index 0000000..7e324a4
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/ajax/StatelessPage.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ajax;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.StatelessForm;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+
+/**
+ * Homepage
+ */
+public class StatelessPage extends WebPage
+{
+	public static final String AJAX_SUBMIT = "AJAX submit";
+
+	public static final String FORM_SUBMIT = "form submit";
+
+	static int itemCount = 0;
+
+	private static final long serialVersionUID = 1L;
+
+	static List<String> getList()
+	{
+		final ArrayList<String> list = new ArrayList<String>(itemCount);
+		final int count = ++itemCount;
+
+		for (int idx = 1; idx <= count; idx++)
+		{
+			list.add(Integer.toString(idx));
+		}
+
+		return list;
+	}
+
+	/**
+	 * Constructor that is invoked when page is invoked without a session.
+	 * 
+	 * @param parameters
+	 *            Page parameters
+	 */
+	public StatelessPage(final PageParameters parameters)
+	{
+		super(parameters);
+
+		final MarkupContainer list = new WebMarkupContainer("list");
+		final List<String> data = getList();
+		final ListView<String> listView = new ListView<String>("item", data)
+		{
+			private static final long serialVersionUID = 200478523599165606L;
+
+			@Override
+			protected void populateItem(final ListItem<String> item)
+			{
+				final String _item = item.getModelObject();
+
+				item.add(new Label("value", _item));
+			}
+		};
+		final Link<String[]> moreLink = new AjaxFallbackLink<String[]>("more")
+		{
+			private static final long serialVersionUID = -1023445535126577565L;
+
+			@Override
+			public void onClick(final AjaxRequestTarget target)
+			{
+				final List<String> _data = getList();
+
+				System.out.println(_data);
+
+				listView.setModelObject(_data);
+
+				if (target != null)
+				{
+					target.add(list, "new");
+				}
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		};
+		final Link<String> homeLink = new BookmarkablePageLink<String>("home", StatelessPage.class);
+
+		add(homeLink);
+		list.add(listView);
+		add(list);
+		add(moreLink);
+
+		// add form
+		TextField<String> name = new TextField<String>("name", new Model<String>("name"));
+		TextField<String> surname = new TextField<String>("surname", new Model<String>("surname"));
+
+		Form<String> form = new StatelessForm<String>("inputForm")
+		{
+			/**
+			 * 
+			 */
+			private static final long serialVersionUID = -6554405700693024016L;
+
+			@Override
+			protected void onSubmit()
+			{
+				super.onSubmit();
+				info(FORM_SUBMIT);
+			}
+		};
+
+		form.add(name, surname);
+
+		final FeedbackPanel feedback;
+		form.add(feedback = new FeedbackPanel("feedback"));
+		feedback.setOutputMarkupId(true);
+
+		form.add(new AjaxSubmitLink("submit")
+		{
+			/**
+			 * 
+			 */
+			private static final long serialVersionUID = -7296676299335203926L;
+
+			@Override
+			protected void onSubmit(AjaxRequestTarget target, Form<?> form)
+			{
+				super.onSubmit(target, form);
+				info(AJAX_SUBMIT);
+				target.add(feedback);
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		});
+		add(form);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/StatelessAjaxFallbackLinkTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/StatelessAjaxFallbackLinkTest.java b/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/StatelessAjaxFallbackLinkTest.java
new file mode 100644
index 0000000..5cf7ff2
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/StatelessAjaxFallbackLinkTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ajax.markup.html;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.ajax.StatelessPage;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class StatelessAjaxFallbackLinkTest
+{
+	protected WicketTester tester;
+
+	@Before
+	public void setUp()
+	{
+		tester = new WicketTester(new MockApplication());
+	}
+
+	@After
+	public void teardown()
+	{
+		// things must stay stateless
+		assertTrue(Session.get().isTemporary());
+	}
+
+	@Test
+	@SuppressWarnings("unchecked")
+	public void testGetStatelessHint()
+	{
+		tester.startPage(StatelessPage.class);
+
+		final StatelessPage page = (StatelessPage)tester.getLastRenderedPage();
+		final AjaxFallbackLink<Void> link = (AjaxFallbackLink<Void>)page.get("more");
+
+		assertTrue(link.isStateless());
+
+		link.onClick();
+
+		final List<? extends Behavior> behaviors = link.getBehaviors();
+		final AjaxEventBehavior behavior = (AjaxEventBehavior)behaviors.get(0);
+
+		behavior.onRequest();
+		
+		assertTrue(link.isStateless());
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/form/StatelessAjaxSubmitLinkTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/form/StatelessAjaxSubmitLinkTest.java b/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/form/StatelessAjaxSubmitLinkTest.java
new file mode 100644
index 0000000..51f7840
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/ajax/markup/html/form/StatelessAjaxSubmitLinkTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ajax.markup.html.form;
+
+import static org.junit.Assert.assertTrue;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.StatelessPage;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.page.XmlPartialPageUpdate;
+import org.apache.wicket.util.tester.FormTester;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class StatelessAjaxSubmitLinkTest
+{
+	protected WicketTester tester;
+
+	@Before
+	public void setUp()
+	{
+		tester = new WicketTester(new MockApplication());
+	}
+
+	@After
+	public void teardown()
+	{
+		// things must stay stateless
+		assertTrue(Session.get().isTemporary());
+	}
+	
+	@Test
+	public void testSubmitForm() throws Exception 
+	{
+    	tester.startPage(StatelessPage.class);
+    	
+    	FormTester formTester = tester.newFormTester("inputForm");
+    	formTester.setValue("name", "myname");
+    	formTester.setValue("surname", "mysurname");
+    	
+    	tester.executeAjaxEvent("inputForm:submit", "click");
+    	
+    	String response = tester.getLastResponseAsString();
+    	
+    	boolean isAjaxResponse = response.contains(XmlPartialPageUpdate.START_ROOT_ELEMENT)
+    		&& response.contains(XmlPartialPageUpdate.END_ROOT_ELEMENT);
+    	
+    	assertTrue(isAjaxResponse);
+    	
+    	boolean formAjaxSubmit = response.contains(StatelessPage.FORM_SUBMIT) &&
+    		response.contains(StatelessPage.AJAX_SUBMIT);
+    	
+    	assertTrue(formAjaxSubmit);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.html
----------------------------------------------------------------------
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.html b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.html
new file mode 100644
index 0000000..1f07813
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.html
@@ -0,0 +1,72 @@
+<!--
+
+     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.
+
+-->
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+	<head>
+	 <title>Stateless Wicket</title>
+	 <link rel="stylesheet" type="text/css" href="style.css"/>
+	</head>
+	<body>
+		<span wicket:id="mainNavigation"></span>
+		<span wicket:id="message" id="message">Message goes here</span>
+		<br />
+		<a wicket:id="indexLink" href="#">go to index</a>
+		<br />
+		<h2>Statless ajax fallback link</h2>
+		<div>This link uses ajax when it is available, and a regular
+			wicket roundtrip when ajax or javascript are not available and the
+			page should be stateless.</div>
+
+		<b>Counter: </b>
+		<span id="c2" wicket:id="incrementLabel"></span>
+		<a href="#" id="c2-link" wicket:id="incrementLink">increment</a>
+		<br />
+		<br />
+
+		<h2>Stateless form submit example</h2>
+		<p>Submit form via AJAX. Each field is required.</p>
+		<form wicket:id="inputForm">
+			<label for="name">Name:</label> <input wicket:id="name" id="name"
+				type="text" size="15" /><br /> <label for="surname">Surname:</label>
+			<input wicket:id="surname" id="surname" type="text" size="15" /><br />
+			<button id="submitButton" wicket:id="submit">Submit</button>
+
+			<div id="feedbackPanel" wicket:id="feedback"></div>
+			<br /> <span id="submittedValues" wicket:id="submittedValues"></span>
+
+		</form>
+		
+		<h2>Form component updating behavior example</h2>
+		<p>The select component submits its value via AJAX on 'change'
+			event.</p>
+		<select id="select" wicket:id="select"></select>
+		<span id="selectedValue" wicket:id="selectedValue"></span>
+		
+		<br />
+		<br />
+		<h2>AJAX indicator link example</h2>
+		<p>AJAX indicator link and button which show their indicator for
+			5 seconds.</p>
+		<a id="indicatingLink" wicket:id="indicatingLink">link</a>
+		<br />
+
+		<form wicket:id="indicatingForm"></form>
+		<button id="indicatingButton" wicket:id="indicatingButton">Button</button>
+	</body>
+</html>

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.java
----------------------------------------------------------------------
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.java b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.java
new file mode 100644
index 0000000..32c5e81
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/AjaxStatelessExample.java
@@ -0,0 +1,220 @@
+package org.apache.wicket.examples.stateless;
+
+import java.util.Arrays;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
+import org.apache.wicket.examples.WicketExamplePage;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxFallbackLink;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.StatelessForm;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.util.string.StringValue;
+
+
+public class AjaxStatelessExample extends WicketExamplePage
+{
+
+	private static final String COUNTER_PARAM = "counter";
+
+	/**
+	 * Constructor that is invoked when page is invoked without a session.
+	 *
+	 * @param parameters
+	 *            Page parameters
+	 */
+	public AjaxStatelessExample(final PageParameters parameters)
+	{
+		super(parameters);
+		setStatelessHint(true);
+		
+		add(new Label("message", new SessionModel()));
+		add(new BookmarkablePageLink<>("indexLink", Index.class));
+		
+		final Label incrementLabel = new Label("incrementLabel", new AbstractReadOnlyModel<Integer>()
+		{
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public Integer getObject()
+			{
+				final String counter = getParameter(parameters, COUNTER_PARAM);
+				return counter != null ? Integer.parseInt(counter) : 0;
+			}
+
+		});
+		final Link<?> incrementLink = new AjaxFallbackLink<Void>("incrementLink")
+		{
+
+			@Override
+			public void onClick(final AjaxRequestTarget target)
+			{				
+				Integer counter = (Integer)incrementLabel.getDefaultModelObject();
+				updateParams(getPageParameters(), counter);
+				
+				target.add(incrementLabel, this);
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		};
+
+		add(incrementLink);
+		add(incrementLabel.setOutputMarkupId(true));
+
+		final TextField<String> nameField = new TextField<String>("name", new Model<String>(""));
+		final TextField<String> surnameField = new TextField<String>("surname", new Model<String>(""));
+
+		final Form<String> form = new StatelessForm<String>("inputForm")
+		{
+
+			@Override
+			protected void onSubmit()
+			{
+
+			}
+
+		};
+		final DropDownChoice<String> select = new DropDownChoice<String>("select",
+			new Model<String>("2"), Arrays.asList(new String[] { "1", "2", "3" }));
+		final Label selectedValue = new Label("selectedValue", "");
+		add(selectedValue.setOutputMarkupId(true));
+
+		select.add(new AjaxFormComponentUpdatingBehavior("change")
+		{
+
+			@Override
+			protected void onUpdate(final AjaxRequestTarget target)
+			{
+				final String value = select.getModelObject();
+				selectedValue.setDefaultModelObject("Selected value: " + value);
+				target.add(selectedValue);
+			}
+			
+			@Override
+			public boolean getStatelessHint(Component component)
+			{
+				return true;
+			}
+		});
+
+		form.add(nameField.setRequired(true));
+		form.add(surnameField.setRequired(true));
+
+		final Component feedback = new FeedbackPanel("feedback");
+		final Label submittedValues = new Label("submittedValues", "");
+
+		form.add(feedback.setOutputMarkupId(true));
+		form.add(submittedValues.setOutputMarkupId(true));
+
+		form.add(new AjaxSubmitLink("submit")
+		{
+			@Override
+			protected void onError(AjaxRequestTarget target, Form<?> form)
+			{
+				super.onError(target, form);
+				target.add(feedback);
+			}
+
+			@Override
+			protected void onSubmit(AjaxRequestTarget target, Form<?> form)
+			{
+				super.onSubmit(target, form);
+				String values = "Your name is: " + nameField.getModelObject() + " " + surnameField.getModelObject();
+				submittedValues.setDefaultModelObject(values);
+				target.add(feedback, submittedValues);
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		});
+
+		add(form);
+
+		add(select);
+
+
+		add(new IndicatingAjaxFallbackLink("indicatingLink")
+		{
+			@Override
+			public void onClick(AjaxRequestTarget target)
+			{
+				try
+				{
+					Thread.sleep(5000); // 1000 milliseconds is one second.
+				}
+				catch (InterruptedException ex)
+				{
+					Thread.currentThread().interrupt();
+				}
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		});
+
+		StatelessForm indicatingForm = new StatelessForm("indicatingForm");
+
+		add(indicatingForm);
+		add(new IndicatingAjaxButton("indicatingButton", indicatingForm)
+		{
+			@Override
+			protected void onSubmit(AjaxRequestTarget target, Form<?> form)
+			{
+				try
+				{
+					Thread.sleep(5000); // 1000 milliseconds is one second.
+				}
+				catch (InterruptedException ex)
+				{
+					Thread.currentThread().interrupt();
+				}
+			}
+			
+			@Override
+			protected boolean getStatelessHint()
+			{
+				return true;
+			}
+		});
+
+	}
+
+	private String getParameter(final PageParameters parameters, final String key)
+	{
+		final StringValue value = parameters.get(key);
+
+		if (value.isNull() || value.isEmpty())
+		{
+			return null;
+		}
+
+		return value.toString();
+	}
+
+	protected final void updateParams(final PageParameters pageParameters, final int counter)
+	{
+		pageParameters.set(COUNTER_PARAM, Integer.toString(counter + 1));
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.html
----------------------------------------------------------------------
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.html b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.html
index 4c4a4fb..4239cc9 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.html
@@ -18,6 +18,9 @@
 		<br />
 		<a wicket:id="linkToStatefulPage" href="#">go to a stateful page (triggers session creation if not already done so)</a>
 		<br />
+		<a wicket:id="linkToAjaxExamples" href="#">Stateless AJAX Example</a>
+		<br/>
+		<br/>
 		<a wicket:id="invalidatesession" href="#">Invalidate the session</a>
 	</body>
 </html>

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.java
----------------------------------------------------------------------
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.java b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.java
index 5b86437..95b7dea 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/stateless/Index.java
@@ -61,6 +61,19 @@ public class Index extends WicketExamplePage
 				setResponsePage(StatefulPage.class);
 			}
 		});
+		add(new StatelessLink<Void>("linkToAjaxExamples")
+		{
+			private static final long serialVersionUID = 1L;
+			
+			/**
+			 * @see org.apache.wicket.markup.html.link.Link#onClick()
+			 */
+			@Override
+			public void onClick()
+			{
+				setResponsePage(AjaxStatelessExample.class);
+			}
+		});
 		add(new StatelessLink<Void>("invalidatesession")
 		{
 			private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-user-guide/src/docs/guide/ajax/ajax_7.gdoc
----------------------------------------------------------------------
diff --git a/wicket-user-guide/src/docs/guide/ajax/ajax_7.gdoc b/wicket-user-guide/src/docs/guide/ajax/ajax_7.gdoc
index 83650d4..ea1d469 100644
--- a/wicket-user-guide/src/docs/guide/ajax/ajax_7.gdoc
+++ b/wicket-user-guide/src/docs/guide/ajax/ajax_7.gdoc
@@ -1,7 +1,44 @@
+Wicket makes working with AJAX easy and pleasant with its component-oriented abstraction. However as side effect, AJAX components and behaviors make their hosting page stateful. This can be quite annoying if we are working on a page that must be stateless (for example a login page).
+Starting from version 7.4.0 Wicket has made quite easy forcing existing AJAX components to be stateless. All we have to do is to override component's method @getStatelessHint@ returning true:
 
+{code}
+final Link<?> incrementLink = new AjaxFallbackLink<Void>("incrementLink")
+{
 
-AJAX is another example of how Wicket can simplify web technologies providing a good component and object oriented abstraction of them. 
+    ...
+    
+    @Override
+    protected boolean getStatelessHint()
+    {
+        return true;
+    }
+};
+{code}
 
-In this chapter we have seen how to take advantage of the AJAX support provided by Wicket to write AJAX-enhanced applications. Most of the chapter has been dedicated to the built-in components and behaviors that let us adopt AJAX without almost any effort. 
 
-In the final part of the chapter we have seen how Wicket physically implements an AJAX call on client side using AJAX request attributes. Then, we have learnt how to use call listeners to execute custom JavaScript during AJAX request lifecycle.
+Just like components also AJAX behaviors can be turned to stateless overriding @getStatelessHint(Component component)@
+
+{code}
+ final AjaxFormSubmitBehavior myBehavior = new AjaxFormSubmitBehavior(form, event)
+ {
+    ...
+    
+    @Override
+    protected boolean getStatelessHint(Component component)
+    {
+        return true;
+    }
+};
+{code}
+
+h3. Usage
+
+Stateless components and behaviors follows the same rules and conventions of their standard stateful version, so they must have a markup id in order to be manipulated via JavaScript.
+However in this case calling @setOutputMarkupId@ on a component is not enough. Since we are working with a stateless page, the id of the component to refresh must be unique but also static, meaning that it should not depend on page instance. In other words, the id should be constant through different instances of the same page.
+By default calling @setOutputMarkupId@ we generate markup ids using a session-level counter and this make them not static. Hence, to refresh component in a stateless page we must provide them with static ids, either setting them in Java code (with @Component.setMarkupId@) or simply writing them directly in the markup:
+
+{code}
+   <span id="staticIdToUse" wicket:id="componentWicketId"></span>
+{code}
+
+{externalink:wicket.examples.url@stateless}See examples{externalink} page for a full showcase of AJAX-stateless capabilities.

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-user-guide/src/docs/guide/ajax/ajax_8.gdoc
----------------------------------------------------------------------
diff --git a/wicket-user-guide/src/docs/guide/ajax/ajax_8.gdoc b/wicket-user-guide/src/docs/guide/ajax/ajax_8.gdoc
new file mode 100644
index 0000000..83650d4
--- /dev/null
+++ b/wicket-user-guide/src/docs/guide/ajax/ajax_8.gdoc
@@ -0,0 +1,7 @@
+
+
+AJAX is another example of how Wicket can simplify web technologies providing a good component and object oriented abstraction of them. 
+
+In this chapter we have seen how to take advantage of the AJAX support provided by Wicket to write AJAX-enhanced applications. Most of the chapter has been dedicated to the built-in components and behaviors that let us adopt AJAX without almost any effort. 
+
+In the final part of the chapter we have seen how Wicket physically implements an AJAX call on client side using AJAX request attributes. Then, we have learnt how to use call listeners to execute custom JavaScript during AJAX request lifecycle.

http://git-wip-us.apache.org/repos/asf/wicket/blob/b7fb9617/wicket-user-guide/src/docs/guide/toc.yml
----------------------------------------------------------------------
diff --git a/wicket-user-guide/src/docs/guide/toc.yml b/wicket-user-guide/src/docs/guide/toc.yml
index b0e7631..537ae51 100644
--- a/wicket-user-guide/src/docs/guide/toc.yml
+++ b/wicket-user-guide/src/docs/guide/toc.yml
@@ -154,7 +154,8 @@ ajax:
   ajax_4: Using an activity indicator
   ajax_5: AJAX request attributes and call listeners
   ajax_6: Creating custom AJAX call listener
-  ajax_7: Summary
+  ajax_7: Stateless AJAX components/behaviors
+  ajax_8: Summary
 jee:
   title: Integration with enterprise containers
   jee_1: Integrating Wicket with EJB