Updated Branches:
  refs/heads/master ad06b208b -> 048489860

WICKET-5411 auto label auto update during ajax


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

Branch: refs/heads/master
Commit: 0484898603fb5cb726aa11caa6c7346a67faa5ed
Parents: ad06b20
Author: Igor Vaynberg <[email protected]>
Authored: Fri Nov 8 22:48:22 2013 -0800
Committer: Igor Vaynberg <[email protected]>
Committed: Fri Nov 8 23:10:54 2013 -0800

----------------------------------------------------------------------
 .../form/AjaxFormComponentUpdatingBehavior.java |   2 +-
 .../markup/html/form/AutoLabelResolver.java     | 131 +++++++++++++++++--
 .../apache/wicket/markup/html/form/Form.java    |  24 +++-
 .../wicket/markup/html/form/FormComponent.java  |  29 +++-
 .../wicket/protocol/http/WebApplication.java    |  36 +++--
 .../html/form/AutoFormLabelPickupTest.java      |   4 +-
 .../html/form/AutoLabelWithContentTest.java     |   4 +-
 .../html/form/AutoLabelWithinEnclosureTest.java |   2 +-
 8 files changed, 194 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormComponentUpdatingBehavior.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormComponentUpdatingBehavior.java
 
b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormComponentUpdatingBehavior.java
index 2d6cf0e..934604b 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormComponentUpdatingBehavior.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormComponentUpdatingBehavior.java
@@ -161,8 +161,8 @@ public abstract class AjaxFormComponentUpdatingBehavior 
extends AjaxEventBehavio
                catch (RuntimeException e)
                {
                        onError(target, e);
-
                }
+               formComponent.updateAutoLabels(target);
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/main/java/org/apache/wicket/markup/html/form/AutoLabelResolver.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/AutoLabelResolver.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/AutoLabelResolver.java
index 723bb83..cc0f1cc 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/AutoLabelResolver.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/AutoLabelResolver.java
@@ -16,9 +16,13 @@
  */
 package org.apache.wicket.markup.html.form;
 
+import java.io.Serializable;
+
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.MetaDataKey;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.core.util.string.CssUtils;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
@@ -37,11 +41,14 @@ import org.slf4j.LoggerFactory;
  * <li>Outputs the {@code for} attribute with the value equivalent to the 
markup id of the
  * referenced form component</li>
  * <li>Appends {@code required} css class to the {@code <label>} tag if the 
referenced form
- * component is required</li>
+ * component is required. Name of the css class can be overwritten by having a 
i18n property defined
+ * for key AutoLabel.CSS.required</li>
  * <li>Appends {@code error} css class to the {@code <label>} tag if the 
referenced form component
- * has failed validation</li>
+ * has failed validation. Name of the css class can be overwritten by having a 
i18n property defined
+ * for key AutoLabel.CSS.error</li>
  * <li>Appends {@code disabled} css class to the {@code <label>} tag if the 
referenced form
- * component has is not enabled in hierarchy</li>
+ * component has is not enabled in hierarchy. Name of the css class can be 
overwritten by having a i18n property defined
+ * for key AutoLabel.CSS.disabled</li>
  * </ul>
  * 
  * <p>
@@ -63,13 +70,15 @@ public class AutoLabelResolver implements IComponentResolver
 
        private static final Logger logger = 
LoggerFactory.getLogger(AutoLabelResolver.class);
 
-       public static final String REQUIRED_CSS_CLASS_KEY = 
CssUtils.key(AutoLabel.class, "required");
-
-       public static final String INVALID_CSS_CLASS_KEY = 
CssUtils.key(AutoLabel.class, "invalid");
+       static final String WICKET_FOR = ":for";
 
-       public static final String DISABLED_CSS_CLASS_KEY = 
CssUtils.key(AutoLabel.class, "disabled");
+       public static final String CSS_REQUIRED_KEY = 
CssUtils.key(AutoLabel.class, "requried");
+       public static final String CSS_DISABLED_KEY = 
CssUtils.key(AutoLabel.class, "requried");
+       public static final String CSS_ERROR_KEY = 
CssUtils.key(AutoLabel.class, "requried");
+       private static final String CSS_DISABLED_DEFAULT = "disabled";
+       private static final String CSS_REQUIRED_DEFAULT = "required";
+       private static final String CSS_ERROR_DEFAULT = "error";
 
-       static final String WICKET_FOR = ":for";
 
        @Override
        public Component resolve(final MarkupContainer container, final 
MarkupStream markupStream,
@@ -106,6 +115,11 @@ public class AutoLabelResolver implements 
IComponentResolver
                        }
                }
 
+               if (component instanceof FormComponent)
+               {
+                       component.setMetaData(MARKER_KEY, new 
AutoLabelMarker((FormComponent<?>)component));
+               }
+
                return new AutoLabel("label" + 
container.getPage().getAutoIndex(), component);
        }
 
@@ -168,6 +182,100 @@ public class AutoLabelResolver implements 
IComponentResolver
                return null;
        }
 
+       public static final String getLabelIdFor(Component component)
+       {
+               return component.getMarkupId() + "-w-lbl";
+       }
+
+       public static final MetaDataKey<AutoLabelMarker> MARKER_KEY = new 
MetaDataKey<AutoLabelMarker>()
+       {
+       };
+
+       /**
+        * Marker used to track whether or not a form component has an 
associated auto label by its mere
+        * presense as well as some attributes of the component across requests.
+        * 
+        * @author igor
+        * 
+        */
+       public static final class AutoLabelMarker implements Serializable
+       {
+               public static final short VALID = 0x01;
+               public static final short REQUIRED = 0x02;
+               public static final short ENABLED = 0x04;
+
+               private short flags;
+
+               public AutoLabelMarker(FormComponent<?> component)
+               {
+                       setFlag(VALID, component.isValid());
+                       setFlag(REQUIRED, component.isRequired());
+                       setFlag(ENABLED, component.isEnabledInHierarchy());
+               }
+
+               public void updateFrom(FormComponent<?> component, 
AjaxRequestTarget target)
+               {
+                       boolean valid = component.isValid(), required = 
component.isRequired(), enabled = component.isEnabledInHierarchy();
+
+                       if (isValid() != valid)
+                       {
+                               
target.appendJavaScript(String.format("$('#%s').toggleClass('%s', %s);",
+                                       getLabelIdFor(component), 
component.getString(CSS_ERROR_KEY, null, CSS_ERROR_DEFAULT),
+                                       !valid));
+                       }
+
+                       if (isRequired() != required)
+                       {
+                               
target.appendJavaScript(String.format("$('#%s').toggleClass('%s', %s);",
+                                       getLabelIdFor(component), 
component.getString(CSS_REQUIRED_KEY, null, CSS_REQUIRED_DEFAULT),
+                                       required));
+                       }
+
+                       if (isEnabled() != enabled)
+                       {
+                               
target.appendJavaScript(String.format("$('#%s').toggleClass('%s', %s);",
+                                       getLabelIdFor(component), 
component.getString(CSS_DISABLED_KEY, null, CSS_DISABLED_DEFAULT),
+                                       !enabled));
+                       }
+
+                       setFlag(VALID, valid);
+                       setFlag(REQUIRED, required);
+                       setFlag(ENABLED, enabled);
+               }
+
+               public boolean isValid()
+               {
+                       return getFlag(VALID);
+               }
+
+               public boolean isEnabled()
+               {
+                       return getFlag(ENABLED);
+               }
+
+               public boolean isRequired()
+               {
+                       return getFlag(REQUIRED);
+               }
+
+               private boolean getFlag(final int flag)
+               {
+                       return (flags & flag) != 0;
+               }
+
+               private void setFlag(final short flag, final boolean set)
+               {
+                       if (set)
+                       {
+                               flags |= flag;
+                       }
+                       else
+                       {
+                               flags &= ~flag;
+                       }
+               }
+       }
+
        /**
         * Component that is attached to the {@code <label>} tag and takes care 
of writing out the label
         * text as well as setting classes on the {@code <label>} tag
@@ -191,23 +299,24 @@ public class AutoLabelResolver implements 
IComponentResolver
                {
                        super.onComponentTag(tag);
                        tag.put("for", component.getMarkupId());
+                       tag.put("id", getLabelIdFor(component));
 
                        if (component instanceof FormComponent)
                        {
                                FormComponent<?> fc = 
(FormComponent<?>)component;
                                if (fc.isRequired())
                                {
-                                       tag.append("class", 
getString(REQUIRED_CSS_CLASS_KEY), " ");
+                                       tag.append("class", 
component.getString(CSS_REQUIRED_KEY, null, CSS_REQUIRED_DEFAULT), " ");
                                }
                                if (!fc.isValid())
                                {
-                                       tag.append("class", 
getString(INVALID_CSS_CLASS_KEY), " ");
+                                       tag.append("class", 
component.getString(CSS_ERROR_KEY, null, CSS_ERROR_DEFAULT), " ");
                                }
                        }
 
                        if (!component.isEnabledInHierarchy())
                        {
-                               tag.append("class", 
getString(DISABLED_CSS_CLASS_KEY), " ");
+                               tag.append("class", 
component.getString(CSS_DISABLED_KEY, null, CSS_DISABLED_DEFAULT), " ");
                        }
                }
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java
index 5fac05b..93364e7 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java
@@ -30,6 +30,7 @@ import org.apache.wicket.Component;
 import org.apache.wicket.IGenericComponent;
 import org.apache.wicket.Page;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.behavior.Behavior;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
@@ -144,10 +145,8 @@ import org.slf4j.LoggerFactory;
  * @param <T>
  *            The model object type
  */
-public class Form<T> extends WebMarkupContainer
-       implements
-               IFormSubmitListener,
-               IGenericComponent<T>
+public class Form<T> extends WebMarkupContainer implements IFormSubmitListener,
+       IGenericComponent<T>
 {
        private static final String HIDDEN_DIV_START = "<div 
style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
 
@@ -775,6 +774,20 @@ public class Form<T> extends WebMarkupContainer
                {
                        callOnError(submitter);
                }
+
+
+               if (((WebRequest)getRequest()).isAjax())
+               {
+                       final AjaxRequestTarget target = 
getRequestCycle().find(AjaxRequestTarget.class);
+                       visitChildren(FormComponent.class, new 
IVisitor<FormComponent<?>, Void>()
+                       {
+                               @Override
+                               public void component(FormComponent<?> 
component, IVisit<Void> visit)
+                               {
+                                       component.updateAutoLabels(target);
+                               }
+                       });
+               }
        }
 
        /**
@@ -2082,7 +2095,8 @@ public class Form<T> extends WebMarkupContainer
         * 
         * @author igor
         */
-       public static enum MethodMismatchResponse {
+       public static enum MethodMismatchResponse
+       {
                /**
                 * Continue processing.
                 */

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/main/java/org/apache/wicket/markup/html/form/FormComponent.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/FormComponent.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/FormComponent.java
index 1a1a6d4..1744060 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/FormComponent.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/FormComponent.java
@@ -36,12 +36,15 @@ import org.apache.wicket.IConverterLocator;
 import org.apache.wicket.IGenericComponent;
 import org.apache.wicket.Localizer;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.behavior.Behavior;
 import org.apache.wicket.core.util.lang.WicketObjects;
 import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.form.AutoLabelResolver.AutoLabelMarker;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.IPropertyReflectionAwareModel;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.util.convert.ConversionException;
 import org.apache.wicket.util.convert.IConverter;
 import org.apache.wicket.util.lang.Args;
@@ -98,11 +101,8 @@ import org.slf4j.LoggerFactory;
  *            The model object type
  * 
  */
-public abstract class FormComponent<T> extends LabeledWebMarkupContainer
-       implements
-               IFormVisitorParticipant,
-               IFormModelUpdateListener,
-               IGenericComponent<T>
+public abstract class FormComponent<T> extends LabeledWebMarkupContainer 
implements
+       IFormVisitorParticipant, IFormModelUpdateListener, IGenericComponent<T>
 {
        private static final Logger logger = 
LoggerFactory.getLogger(FormComponent.class);
 
@@ -1579,6 +1579,25 @@ public abstract class FormComponent<T> extends 
LabeledWebMarkupContainer
        }
 
        /**
+        * Updates auto label css classes such as error/required during ajax 
updates when the labels may
+        * not be directly repainted in the response.
+        * 
+        * @param target
+        */
+       public final void updateAutoLabels(AjaxRequestTarget target)
+       {
+               AutoLabelMarker marker = 
getMetaData(AutoLabelResolver.MARKER_KEY);
+       
+               if (marker == null)
+               {
+                       // this component does not have an auto label
+                       return;
+               }
+
+               marker.updateFrom(this, target);
+       }
+
+       /**
         * Update the model of a {@link FormComponent} containing a {@link 
Collection}.
         * 
         * If the model object does not yet exists, a new {@link ArrayList} is 
filled with the converted

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java 
b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
index ba69791..22bbfda 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
@@ -394,10 +394,10 @@ public abstract class WebApplication extends Application
        /**
         * Registers a replacement resource for the given javascript resource. 
This replacement can be
         * another {@link JavaScriptResourceReference} for a packaged resource, 
but it can also be an
-        * {@link org.apache.wicket.request.resource.UrlResourceReference} to 
replace the resource by a resource hosted on a CDN.
-        * Registering a replacement will cause the resource to replaced by the 
given resource
-        * throughout the application: if {@code base} is added, {@code 
replacement} will be added
-        * instead.
+        * {@link org.apache.wicket.request.resource.UrlResourceReference} to 
replace the resource by a
+        * resource hosted on a CDN. Registering a replacement will cause the 
resource to replaced by
+        * the given resource throughout the application: if {@code base} is 
added, {@code replacement}
+        * will be added instead.
         * 
         * @param base
         *            The resource to replace
@@ -415,10 +415,10 @@ public abstract class WebApplication extends Application
        /**
         * Registers a replacement resource for the given CSS resource. This 
replacement can be another
         * {@link CssResourceReference} for a packaged resource, but it can 
also be an
-        * {@link org.apache.wicket.request.resource.UrlResourceReference} to 
replace the resource by a resource hosted on a CDN.
-        * Registering a replacement will cause the resource to replaced by the 
given resource
-        * throughout the application: if {@code base} is added, {@code 
replacement} will be added
-        * instead.
+        * {@link org.apache.wicket.request.resource.UrlResourceReference} to 
replace the resource by a
+        * resource hosted on a CDN. Registering a replacement will cause the 
resource to replaced by
+        * the given resource throughout the application: if {@code base} is 
added, {@code replacement}
+        * will be added instead.
         * 
         * @param base
         *            The resource to replace
@@ -951,9 +951,8 @@ public abstract class WebApplication extends Application
                return ajaxRequestTargetListeners;
        }
 
-       private static class DefaultAjaxRequestTargetProvider
-               implements
-                       IContextProvider<AjaxRequestTarget, Page>
+       private static class DefaultAjaxRequestTargetProvider implements
+               IContextProvider<AjaxRequestTarget, Page>
        {
                @Override
                public AjaxRequestTarget get(Page page)
@@ -981,4 +980,19 @@ public abstract class WebApplication extends Application
                }
                return filterFactoryManager;
        }
+
+       /**
+        * If true, auto label css classes such as {@code error} and {@code 
required} will be updated
+        * after form component processing during an ajax request. This allows 
auto labels to correctly
+        * reflect the state of the form component even if they are not part of 
the ajax markup update.
+        * 
+        * TODO in wicket-7 this should move into a settings object. cannot 
move in 6.x because it
+        * requires a change to a setting interface.
+        * 
+        * @return {@code true} iff enabled
+        */
+       public boolean getUpdateAutoLabelsOnAjaxRequests()
+       {
+               return true;
+       }
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoFormLabelPickupTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoFormLabelPickupTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoFormLabelPickupTest.java
index beb3b32..b47f570 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoFormLabelPickupTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoFormLabelPickupTest.java
@@ -65,14 +65,14 @@ public class AutoFormLabelPickupTest extends WicketTestCase
        public void labelIsPrintedFromModel() throws Exception
        {
                tester.startPage(new PrintLabelPage(Model.of("label from 
model")));
-               tester.assertContains("<label wicket:for=\"input\" 
for=\"input2\">\\|label from model\\|</label>");
+               tester.assertContains("<label wicket:for=\"input\" 
for=\"input2\" id=\"input2-w-lbl\">\\|label from model\\|</label>");
        }
 
        @Test
        public void labelIsPrintedFromProperties() throws Exception
        {
                tester.startPage(new PrintLabelPage(Model.of((String)null)));
-               tester.assertContains("<label wicket:for=\"input\" 
for=\"input2\">\\|label from properties\\|</label>");
+               tester.assertContains("<label wicket:for=\"input\" 
for=\"input2\" id=\"input2-w-lbl\">\\|label from properties\\|</label>");
        }
 
        @Test

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithContentTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithContentTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithContentTest.java
index 46d4cc1..303fa83 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithContentTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithContentTest.java
@@ -45,7 +45,7 @@ public class AutoLabelWithContentTest extends WicketTestCase
        public void labelWithMessage() throws Exception
        {
                tester.startPage(LabelWithMessagePage.class);
-               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\"><wicket:message key=\"foo\">my test 
text</wicket:message></label>");
+               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\" id=\"textfield2-w-lbl\"><wicket:message key=\"foo\">my test 
text</wicket:message></label>");
        }
 
        public static class LabelWithNestedComponentsPage extends WebPage
@@ -62,6 +62,6 @@ public class AutoLabelWithContentTest extends WicketTestCase
        public void labelWithNestedComponent()
        {
                tester.startPage(LabelWithNestedComponentsPage.class);
-               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\"><input type=\"text\" wicket:id=\"textfield\" value=\"\" 
name=\"textfield\" id=\"textfield2\"/></label>");
+               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\" id=\"textfield2-w-lbl\"><input type=\"text\" 
wicket:id=\"textfield\" value=\"\" name=\"textfield\" 
id=\"textfield2\"/></label>");
        }
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/04848986/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithinEnclosureTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithinEnclosureTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithinEnclosureTest.java
index 4c00b2f..5429900 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithinEnclosureTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/AutoLabelWithinEnclosureTest.java
@@ -46,7 +46,7 @@ public class AutoLabelWithinEnclosureTest extends 
WicketTestCase
        {
                tester.startPage(new LabelWithinEnclosurePage(true));
                tester.dumpPage();
-               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\">blabla</label>");
+               tester.assertContains("<label wicket:for=\"textfield\" 
for=\"textfield2\" id=\"textfield2-w-lbl\">blabla</label>");
        }
 
        @Test

Reply via email to