Repository: tapestry-5 Updated Branches: refs/heads/master ad5cca595 -> ef0d1448a
Fix bugs related to field validation inside a BeanEditor component Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/ef0d1448 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/ef0d1448 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/ef0d1448 Branch: refs/heads/master Commit: ef0d1448aca3bca3ed4f8aea6fbedd6b77438fcc Parents: ad5cca5 Author: Howard M. Lewis Ship <hls...@apache.org> Authored: Mon Feb 2 16:39:40 2015 -0800 Committer: Howard M. Lewis Ship <hls...@apache.org> Committed: Mon Feb 2 16:39:40 2015 -0800 ---------------------------------------------------------------------- .../tapestry5/corelib/base/AbstractField.java | 10 +++ .../corelib/components/BeanEditor.java | 20 +++-- .../tapestry5/corelib/components/Form.java | 4 +- .../corelib/pages/PropertyEditBlocks.java | 83 +++++++++++++++----- .../internal/BeanValidationContext.java | 22 ++++-- .../internal/BeanValidationContextImpl.java | 20 +++-- 6 files changed, 113 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java index 851de73..4f977d0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java @@ -364,6 +364,16 @@ public abstract class AbstractField implements Field2 beanValidationContext.setCurrentProperty(null); } + /** + * The validation id is used by Tapestry to coordinate an incoming request (with input for fields, + * an validation errors) with a render of the form containing a field. Normally, a validationId is assigned + * on first access and persists for the remainder of the request; however the {@link org.apache.tapestry5.corelib.components.BeanEditor} + * (or rather, the components used by the BeanEditor) may need to override this as the same fields render and re-render + * multiple times in the same request. + * + * @since 5.4 + */ + @Parameter private String validationId; @Override http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java index 03344fe..c44c9fa 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/BeanEditor.java @@ -1,5 +1,3 @@ -// Copyright 2007, 2008, 2010, 2011 The Apache Software Foundation -// // Licensed 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 @@ -35,13 +33,13 @@ import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.Environment; import org.apache.tapestry5.services.FormSupport; -import java.lang.annotation.Annotation; +import java.util.UUID; /** * A component that generates a user interface for editing the properties of a bean. This is the central component of * the {@link BeanEditForm}, and utilizes a {@link PropertyEditor} for much of its functionality. This component places * a {@link BeanEditContext} into the environment. - * + * * @tapestrydoc */ @SupportsInformalParameters @@ -183,6 +181,8 @@ public class BeanEditor formSupport.storeAndExecute(this, CLEANUP_ENVIRONMENT); } + private String validationId; + /** * Used to initialize the model if necessary, to instantiate the object being edited if necessary, and to push the * BeanEditContext into the environment. @@ -206,8 +206,7 @@ public class BeanEditor try { object = model.newInstance(); - } - catch (Exception ex) + } catch (Exception ex) { String message = String.format("Exception instantiating instance of %s (for component '%s'): %s", PlasticUtils.toTypeName(model.getBeanType()), resources.getCompleteId(), ex); @@ -215,13 +214,18 @@ public class BeanEditor } } + // Set to a new value on each request; the value lasts until the end of the request. + if (validationId == null) { + validationId = UUID.randomUUID().toString(); + } + BeanEditContext context = new BeanEditContextImpl(model.getBeanType()); cachedObject = object; environment.push(BeanEditContext.class, context); // TAP5-2101: Always provide a new BeanValidationContext - environment.push(BeanValidationContext.class, new BeanValidationContextImpl(object)); + environment.push(BeanValidationContext.class, new BeanValidationContextImpl(object, validationId)); } void cleanupEnvironment() @@ -232,7 +236,7 @@ public class BeanEditor // For testing void inject(ComponentResources resources, PropertyOverrides overrides, BeanModelSource source, - Environment environment) + Environment environment) { this.resources = resources; this.overrides = overrides; http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java index 78d5615..ea40106 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java @@ -353,7 +353,7 @@ public class Form implements ClientElement, FormValidationControl resources.triggerEvent(EventConstants.PREPARE, context, null); // Push BeanValidationContext only after the container had a chance to prepare - environment.push(BeanValidationContext.class, new BeanValidationContextImpl(validate)); + environment.push(BeanValidationContext.class, new BeanValidationContextImpl(validate, null)); // Save the form element for later, in case we want to write an encoding // type attribute. @@ -509,7 +509,7 @@ public class Form implements ClientElement, FormValidationControl return true; } - environment.push(BeanValidationContext.class, new BeanValidationContextImpl(validate)); + environment.push(BeanValidationContext.class, new BeanValidationContextImpl(validate, null)); didPushBeanValidationContext = true; http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PropertyEditBlocks.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PropertyEditBlocks.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PropertyEditBlocks.java index 19326cc..2013319 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PropertyEditBlocks.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/PropertyEditBlocks.java @@ -19,6 +19,7 @@ import org.apache.tapestry5.ValueEncoder; import org.apache.tapestry5.annotations.Component; import org.apache.tapestry5.annotations.Environmental; import org.apache.tapestry5.corelib.components.*; +import org.apache.tapestry5.internal.BeanValidationContext; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.services.TypeCoercer; import org.apache.tapestry5.services.BeanBlockContribution; @@ -39,56 +40,96 @@ public class PropertyEditBlocks @Environmental private PropertyEditContext context; + @Environmental + private BeanValidationContext validationContext; + + public String getValidationId() + { + return validationContext.getEditorValidationId() + "-" + context.getPropertyId(); + } + @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", - "translate=prop:textFieldTranslator", "validate=prop:textFieldValidator", - "clientId=prop:context.propertyId", "annotationProvider=context", + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "translate=prop:textFieldTranslator", + "validate=prop:textFieldValidator", + "validationId=validationId", + "clientId=prop:context.propertyId", + "annotationProvider=context", "ensureClientIdUnique=true"}) private TextField textField; @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", - "translate=prop:numberFieldTranslator", "validate=prop:numberFieldValidator", - "clientId=prop:context.propertyId", "annotationProvider=context", + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "translate=prop:numberFieldTranslator", + "validate=prop:numberFieldValidator", + "validationId=validationId", + "clientId=prop:context.propertyId", + "annotationProvider=context", "ensureClientIdUnique=true"}) private TextField numberField; @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", "encoder=valueEncoderForProperty", - "model=selectModelForProperty", "validate=prop:selectValidator", - "clientId=prop:context.propertyId", "ensureClientIdUnique=true"}) + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "encoder=valueEncoderForProperty", + "validationId=validationId", + "model=selectModelForProperty", + "validate=prop:selectValidator", + "clientId=prop:context.propertyId", + "ensureClientIdUnique=true"}) private Select select; @SuppressWarnings("unused") @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", - "clientId=prop:context.propertyId", "ensureClientIdUnique=true"}) + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "validationId=validationId", + "clientId=prop:context.propertyId", + "ensureClientIdUnique=true"}) private Checkbox checkboxField; @SuppressWarnings("unused") @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", "clientId=prop:context.propertyid", - "validate=prop:dateFieldValidator", "ensureClientIdUnique=true"}) + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "clientId=prop:context.propertyid", + "validate=prop:dateFieldValidator", + "ensureClientIdUnique=true"}) private DateField dateField; @SuppressWarnings("unused") @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", "clientId=prop:context.propertyid", - "validate=prop:calendarFieldValidator", "ensureClientIdUnique=true"}) + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "clientId=prop:context.propertyid", + "validate=prop:calendarFieldValidator", + "validationId=validationId", + "ensureClientIdUnique=true"}) private DateField calendarField; @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", - "translate=prop:passwordFieldTranslator", "validate=prop:passwordFieldValidator", - "clientId=prop:context.propertyId", "annotationProvider=context", "ensureClientIdUnique=true"}) + parameters = {"value=context.propertyValue", + "label=prop:context.label", + "translate=prop:passwordFieldTranslator", + "validate=prop:passwordFieldValidator", + "validationId=validationId", + "clientId=prop:context.propertyId", + "annotationProvider=context", + "ensureClientIdUnique=true"}) private PasswordField passwordField; @Component( - parameters = {"value=context.propertyValue", "label=prop:context.label", + parameters = {"value=context.propertyValue", + "label=prop:context.label", "translate=prop:textAreaTranslator", - "validate=prop:textAreaValidator", "clientId=prop:context.propertyId", - "annotationProvider=context", "ensureClientIdUnique=true"}) + "validationId=validationId", + "validate=prop:textAreaValidator", + "clientId=prop:context.propertyId", + "annotationProvider=context", + "ensureClientIdUnique=true"}) private TextArea textArea; http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContext.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContext.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContext.java index 29c0f85..707101d 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContext.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContext.java @@ -1,5 +1,3 @@ -// Copyright 2009, 2010 The Apache Software Foundation -// // Licensed 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 @@ -15,7 +13,7 @@ package org.apache.tapestry5.internal; /** * Defines a context for validating beans. - * + * * @since 5.2.0 */ public interface BeanValidationContext @@ -24,21 +22,29 @@ public interface BeanValidationContext * Returns the type of the object to validate. This method is needed for client side validation. */ Class getBeanType(); - + /** * Return the object to validate. */ Object getBeanInstance(); - + /** * Returns name of the property to validate. The current name is overwritten by every form field. */ String getCurrentProperty(); - + /** * Sets name of the property to validate. - * - * @param propertyName name of the property + * + * @param propertyName + * name of the property */ void setCurrentProperty(String propertyName); + + /** + * Returns a validation id that should be used as a prefix for any fields inside a {@link org.apache.tapestry5.corelib.components.BeanEditor}. + * + * @since 5.4 + */ + String getEditorValidationId(); } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ef0d1448/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContextImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContextImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContextImpl.java index c52d897..2f3d83c 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContextImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/BeanValidationContextImpl.java @@ -1,5 +1,3 @@ -// Copyright 2010 The Apache Software Foundation -// // Licensed 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 @@ -16,17 +14,21 @@ package org.apache.tapestry5.internal; public class BeanValidationContextImpl implements BeanValidationContext { - private Object bean; + private final Object bean; + + private final String editorValidationId; + private String currentProperty; - public BeanValidationContextImpl(Object bean) + public BeanValidationContextImpl(Object bean, String editorValidationId) { this.bean = bean; + this.editorValidationId = editorValidationId; } public Class getBeanType() { - return bean==null?null:bean.getClass(); + return bean == null ? null : bean.getClass(); } public Object getBeanInstance() @@ -34,14 +36,18 @@ public class BeanValidationContextImpl implements BeanValidationContext return bean; } - public String getCurrentProperty() + public String getCurrentProperty() { return currentProperty; } - public void setCurrentProperty(String propertyName) + public void setCurrentProperty(String propertyName) { this.currentProperty = propertyName; } + public String getEditorValidationId() + { + return editorValidationId; + } }