Repository: tapestry-5 Updated Branches: refs/heads/master fa691e262 -> 280940fb0
TAP5-2391: Properly track Fields in ValidationTracker, even during Ajax requests Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/280940fb Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/280940fb Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/280940fb Branch: refs/heads/master Commit: 280940fb098a4766effe297c20812042723529a8 Parents: fa691e2 Author: Howard M. Lewis Ship <hls...@apache.org> Authored: Mon Jan 5 12:42:39 2015 -0800 Committer: Howard M. Lewis Ship <hls...@apache.org> Committed: Mon Jan 5 12:42:39 2015 -0800 ---------------------------------------------------------------------- .../main/java/org/apache/tapestry5/Field.java | 5 +-- .../main/java/org/apache/tapestry5/Field2.java | 38 ++++++++++++++++++++ .../apache/tapestry5/ValidationTrackerImpl.java | 26 +++++++++----- .../tapestry5/corelib/base/AbstractField.java | 16 ++++++++- .../integration/app1/AjaxGroovyTests.groovy | 12 +++++++ 5 files changed, 86 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/280940fb/tapestry-core/src/main/java/org/apache/tapestry5/Field.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/Field.java b/tapestry-core/src/main/java/org/apache/tapestry5/Field.java index eabc532..c22b28d 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/Field.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/Field.java @@ -1,5 +1,3 @@ -// Copyright 2013 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 @@ -22,6 +20,9 @@ package org.apache.tapestry5; * component's {@link #getControlName()} will only be accurate after it has rendered. In some cases, when generating * JavaScript for example, it is necessary to {@linkplain org.apache.tapestry5.services.Heartbeat#defer(Runnable) wait * until the end of the current Heartbeat} to ensure that all components have had their chance to render. + * <p/> + * Most Fields also implement {@link org.apache.tapestry5.Field2}, which was introduced to bridge a gap related to + * re-rendering a form as part of an Ajax request. */ public interface Field extends ClientElement { http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/280940fb/tapestry-core/src/main/java/org/apache/tapestry5/Field2.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/Field2.java b/tapestry-core/src/main/java/org/apache/tapestry5/Field2.java new file mode 100644 index 0000000..3322325 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/Field2.java @@ -0,0 +1,38 @@ +// 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 +// +// 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.tapestry5; + +/** + * Due to how control names and client ids are allocated inside during an Ajax request, it is difficult to + * to connect input data and field validation errors to the fields, since the control name and client id are different + * during the processing of the submitted form data and during the subsequent render. Starting in 5.4, the + * key used to identify a field inside the {@link org.apache.tapestry5.ValidationTracker is this new validation id, + * which is assigned on first read. + * <p/> + * If a field inplements {@link org.apache.tapestry5.Field} but not Field2, then the control name is used as the + * validation id (which will work correctly during non-Ajax requests). + * <p/> + * This assumes a "flat" field structure, where a given component renders only once (not multiple times, inside + * a {@link org.apache.tapestry5.corelib.components.Loop}. + * + * @since 5.4 + */ +public interface Field2 extends Field +{ + /** + * Returns a request-scoped unique validation id for the field. This returns the same value regardless of how + * many times the field is rendered, which means that the behavior will be incorrect when the + * field component is placed inside a loop. + */ + String getValidationId(); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/280940fb/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java index 33666a4..c77e154 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java @@ -1,5 +1,3 @@ -// Copyright 2006-2013 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,15 +33,15 @@ public final class ValidationTrackerImpl extends BaseOptimizedSessionPersistedOb { private static final long serialVersionUID = -3653306147088451811L; - private final String fieldName; + private final String validationId; private String input; private String errorMessage; - FieldTracker(String fieldName) + FieldTracker(String validationId) { - this.fieldName = fieldName; + this.validationId = validationId; } } @@ -52,6 +50,7 @@ public final class ValidationTrackerImpl extends BaseOptimizedSessionPersistedOb private List<FieldTracker> fieldTrackers; // Rebuilt on-demand + // Keyed on validationId private transient Map<String, FieldTracker> fieldToTracker; @@ -66,12 +65,23 @@ public final class ValidationTrackerImpl extends BaseOptimizedSessionPersistedOb fieldToTracker = CollectionFactory.newMap(); for (FieldTracker ft : fieldTrackers) - fieldToTracker.put(ft.fieldName, ft); + fieldToTracker.put(ft.validationId, ft); + } + + private String getKey(Field field) + { + if (field instanceof Field2) + { + Field2 field2 = (Field2) field; + return field2.getValidationId(); + } + + return field.getControlName(); } private FieldTracker get(Field field) { - String key = field.getControlName(); + String key = getKey(field); refreshFieldToTracker(); @@ -90,7 +100,7 @@ public final class ValidationTrackerImpl extends BaseOptimizedSessionPersistedOb refreshFieldToTracker(); - String key = fieldTracker.fieldName; + String key = fieldTracker.validationId; if (!fieldToTracker.containsKey(key)) { http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/280940fb/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 eec94df..851de73 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 @@ -30,6 +30,7 @@ import org.apache.tapestry5.services.Request; import org.apache.tapestry5.services.javascript.JavaScriptSupport; import java.io.Serializable; +import java.util.UUID; /** * Provides initialization of the clientId and elementName properties. In addition, adds the {@link RenderInformals}, @@ -38,7 +39,7 @@ import java.io.Serializable; * @tapestrydoc */ @SupportsInformalParameters -public abstract class AbstractField implements Field +public abstract class AbstractField implements Field2 { /** * The user presentable label for the field. If not provided, a reasonable label is generated from the component's @@ -362,4 +363,17 @@ public abstract class AbstractField implements Field beanValidationContext.setCurrentProperty(null); } + + private String validationId; + + @Override + public String getValidationId() + { + if (validationId == null) + { + validationId = UUID.randomUUID().toString(); + } + + return validationId; + } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/280940fb/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AjaxGroovyTests.groovy ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AjaxGroovyTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AjaxGroovyTests.groovy index 025db6b..b4531da 100644 --- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AjaxGroovyTests.groovy +++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AjaxGroovyTests.groovy @@ -45,4 +45,16 @@ class AjaxGroovyTests extends App1TestCase { assertText "css=#target > p", "You submitted the form." } + @Test + void ajax_form_validation() { + openLinks "Ajax Validation" + + click SUBMIT + + waitForAjaxRequestsToComplete() + + assertText "css=.form-group.has-error .help-block", "Server-side validation error." + + } + }