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
