You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2014/06/03 18:21:21 UTC

[4/4] git commit: Revised solution for TAP5-2331

Revised solution for TAP5-2331


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/193d022b
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/193d022b
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/193d022b

Branch: refs/heads/master
Commit: 193d022be074d90fec562a9915d72af9b85e9ad4
Parents: 24d47d1
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Tue Jun 3 09:05:48 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Tue Jun 3 09:21:33 2014 -0700

----------------------------------------------------------------------
 .../tapestry5/corelib/base/AbstractField.java   | 132 +++++++++----------
 .../corelib/pages/PropertyEditBlocks.java       |  18 ++-
 .../services/PreSelectedFormNamesService.java   |   6 +-
 .../src/test/app1/MultiZoneUpdateInsideForm.tml |   3 +-
 .../integration/app1/AlertsTests.groovy         |  26 ++--
 .../app1/pages/MultiZoneUpdateInsideForm.java   |   2 -
 .../pages/PerFormValidationMessageDemo.java     |   2 -
 7 files changed, 93 insertions(+), 96 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/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 dc400d2..beb4d1d 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
@@ -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
@@ -14,24 +12,8 @@
 
 package org.apache.tapestry5.corelib.base;
 
-import java.io.Serializable;
-
-import org.apache.tapestry5.BindingConstants;
-import org.apache.tapestry5.ComponentAction;
-import org.apache.tapestry5.ComponentResources;
-import org.apache.tapestry5.Field;
-import org.apache.tapestry5.FieldValidationSupport;
-import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.ValidationDecorator;
-import org.apache.tapestry5.ValidationTracker;
-import org.apache.tapestry5.Validator;
-import org.apache.tapestry5.annotations.AfterRender;
-import org.apache.tapestry5.annotations.BeginRender;
-import org.apache.tapestry5.annotations.Environmental;
-import org.apache.tapestry5.annotations.Mixin;
-import org.apache.tapestry5.annotations.Parameter;
-import org.apache.tapestry5.annotations.SetupRender;
-import org.apache.tapestry5.annotations.SupportsInformalParameters;
+import org.apache.tapestry5.*;
+import org.apache.tapestry5.annotations.*;
 import org.apache.tapestry5.corelib.mixins.DiscardBody;
 import org.apache.tapestry5.corelib.mixins.RenderInformals;
 import org.apache.tapestry5.internal.BeanValidationContext;
@@ -39,12 +21,16 @@ import org.apache.tapestry5.internal.InternalComponentResources;
 import org.apache.tapestry5.internal.services.PreSelectedFormNamesService;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.internal.util.TapestryException;
 import org.apache.tapestry5.services.ComponentDefaultProvider;
 import org.apache.tapestry5.services.Environment;
 import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
+import java.io.Serializable;
+
 /**
  * Provides initialization of the clientId and elementName properties. In addition, adds the {@link RenderInformals},
  * and {@link DiscardBody} mixins.
@@ -80,7 +66,7 @@ public abstract class AbstractField implements Field
 
     @Inject
     protected Environment environment;
-    
+
     @Inject
     @Symbol(SymbolConstants.FORM_FIELD_CSS_CLASS)
     protected String cssClass;
@@ -130,37 +116,32 @@ public abstract class AbstractField implements Field
     private static final ProcessSubmission PROCESS_SUBMISSION_ACTION = new ProcessSubmission();
 
     /**
-     * The id used to generate a page-unique client-side identifier for the component. 
-     * If this parameter is not bound and this component is rendered multiple times in a request
-     * and <code>forceClientAllocation</code> is false (the default),
-     * a suffix will be appended to the to id to ensure uniqueness. Either way, 
-     * its value may be accessed via the {@link #getClientId() clientId property}.
-     * When this parameter is bound, Tapestry considers the user (developer) is taking care of 
-     * providing unique client-side identifiers. Special care should be taken when the
-     * field is inside a Zone.
-     * <br>
-     * <strong>Default value: the component's t:id</strong>.
-     * <br>
-     * <em>This parameter will be ignored if it receives any of these values:</em>
-     * <ul>
-     *   <li>reset</li>
-     *   <li>submit</li>
-     *   <li>id</li>
-     *   <li>method</li>
-     *   <li>action</li>
-     *   <li>onsubmit</li>
-     *   <li>cancel</li>
-     * </ul>
+     * Used to explicitly set the client-side id of the element for this component. Normally this is not
+     * bound (or null) and {@link org.apache.tapestry5.services.javascript.JavaScriptSupport#allocateClientId(org.apache.tapestry5.ComponentResources)}
+     * is used to generate a unique client-id based on the component's id. In some cases, when creating client-side
+     * behaviors, it is useful to explicitly set a unique id for an element using this parameter.
+     * <p/>
+     * Certain values, such as "submit", "method", "reset", etc., will cause client-side conflicts and are not allowed; using such will
+     * cause a runtime exception.
      */
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
-    protected String clientId;
-    
+    private String clientId;
+
     /**
-     * When true, it forces the clientId to be passed through the id allocator to avoid repeated ids
-     * even when the clientId parameter is bound.
+     * A rarely used option that indicates that the actual client id should start with the clientId parameter (if non-null)
+     * but should still pass that Id through {@link org.apache.tapestry5.services.javascript.JavaScriptSupport#allocateClientId(String)}
+     * to generate the final id.
+     * <p/>
+     * An example of this are the components used inside a {@link org.apache.tapestry5.corelib.components.BeanEditor} which
+     * will specify a clientId (based on the property name) but still require that it be unique.
+     * <p/>
+     * Defaults to false.
+     *
+     * @since 5.4
      */
-    @Parameter("false")
-    private boolean forceClientIdAllocation;
+    @Parameter
+    private boolean ensureClientIdUnique;
+
 
     private String assignedClientId;
 
@@ -186,7 +167,7 @@ public abstract class AbstractField implements Field
 
     @Inject
     protected FieldValidationSupport fieldValidationSupport;
-    
+
     @Inject
     private PreSelectedFormNamesService preSelectedFormNamesService;
 
@@ -195,11 +176,6 @@ public abstract class AbstractField implements Field
         return defaultProvider.defaultLabel(resources);
     }
 
-    final String defaultClientId()
-    {
-        return resources.getId();
-    }
-
     public final String getLabel()
     {
         return label;
@@ -208,11 +184,6 @@ public abstract class AbstractField implements Field
     @SetupRender
     final void setup()
     {
-        // By default, use the component id as the (base) client id. If the clientid
-        // parameter is bound, then that is the value to use.
-
-        String id = clientId;
-
         // Often, these controlName and clientId will end up as the same value. There are many
         // exceptions, including a form that renders inside a loop, or a form inside a component
         // that is used multiple times.
@@ -220,16 +191,39 @@ public abstract class AbstractField implements Field
         if (formSupport == null)
             throw new RuntimeException(String.format("Component %s must be enclosed by a Form component.",
                     resources.getCompleteId()));
-        
-        final boolean avoidAllocation = resources.isBound("clientId") && !forceClientIdAllocation && !preSelectedFormNamesService.isPreselected(clientId);
-        assignedClientId = avoidAllocation ? clientId : javaScriptSupport.allocateClientId(id);
-        
-        String controlName = formSupport.allocateControlName(id);
+
+        assignedClientId = allocateClientId();
+
+        String controlName = formSupport.allocateControlName(assignedClientId);
 
         formSupport.storeAndExecute(this, new Setup(controlName));
         formSupport.store(this, PROCESS_SUBMISSION_ACTION);
     }
 
+    private String allocateClientId()
+    {
+        if (clientId == null)
+        {
+            return javaScriptSupport.allocateClientId(resources);
+        }
+
+        if (preSelectedFormNamesService.isPreselected(clientId))
+        {
+            throw new TapestryException(String.format(
+                    "The value '%s' for parameter clientId is not allowed as it causes a naming conflict in the client-side DOM. " +
+                            "Select a name not in the list: %s.",
+                    clientId,
+                    InternalUtils.joinSorted(preSelectedFormNamesService.getNames())), this, null);
+        }
+
+        if (ensureClientIdUnique)
+        {
+            return javaScriptSupport.allocateClientId(clientId);
+        }
+
+        return clientId;
+    }
+
     public final String getClientId()
     {
         return assignedClientId;
@@ -328,7 +322,10 @@ public abstract class AbstractField implements Field
 
     protected void putPropertyNameIntoBeanValidationContext(String parameterName)
     {
-        if (beanValidationDisabled) { return; }
+        if (beanValidationDisabled)
+        {
+            return;
+        }
 
         String propertyName = ((InternalComponentResources) resources).getPropertyName(parameterName);
 
@@ -346,7 +343,10 @@ public abstract class AbstractField implements Field
 
     protected void removePropertyNameFromBeanValidationContext()
     {
-        if (beanValidationDisabled) { return; }
+        if (beanValidationDisabled)
+        {
+            return;
+        }
 
         BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/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 0b92f4b..19326cc 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
@@ -1,5 +1,3 @@
-// Copyright 2007, 2008, 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
@@ -45,52 +43,52 @@ public class PropertyEditBlocks
             parameters = {"value=context.propertyValue", "label=prop:context.label",
                     "translate=prop:textFieldTranslator", "validate=prop:textFieldValidator",
                     "clientId=prop:context.propertyId", "annotationProvider=context",
-                    "forceClientIdAllocation=true"})
+                    "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",
-                    "forceClientIdAllocation=true"})
+                    "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", "forceClientIdAllocation=true"})
+                    "clientId=prop:context.propertyId", "ensureClientIdUnique=true"})
     private Select select;
 
     @SuppressWarnings("unused")
     @Component(
             parameters = {"value=context.propertyValue", "label=prop:context.label",
-                    "clientId=prop:context.propertyId", "forceClientIdAllocation=true"})
+                    "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", "forceClientIdAllocation=true"})
+                    "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", "forceClientIdAllocation=true"})
+                    "validate=prop:calendarFieldValidator", "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", "forceClientIdAllocation=true"})
+                    "clientId=prop:context.propertyId", "annotationProvider=context", "ensureClientIdUnique=true"})
     private PasswordField passwordField;
 
     @Component(
             parameters = {"value=context.propertyValue", "label=prop:context.label",
                     "translate=prop:textAreaTranslator",
                     "validate=prop:textAreaValidator", "clientId=prop:context.propertyId",
-                    "annotationProvider=context", "forceClientIdAllocation=true"})
+                    "annotationProvider=context", "ensureClientIdUnique=true"})
     private TextArea textArea;
 
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PreSelectedFormNamesService.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PreSelectedFormNamesService.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PreSelectedFormNamesService.java
index fc00ba6..379c53b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PreSelectedFormNamesService.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PreSelectedFormNamesService.java
@@ -1,5 +1,3 @@
-// Copyright 2014 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
@@ -18,9 +16,9 @@ import java.util.Set;
 import org.apache.tapestry5.internal.InternalSymbols;
 
 /**
- * Service provinding methods related to names that shouldn't be used as HTML elements client ids.
+ * Service providing methods related to names that shouldn't be used as HTML elements client ids.
  * 
- * @see InternalSymbols.PRE_SELECTED_FORM_NAMES
+ * @see InternalSymbols#PRE_SELECTED_FORM_NAMES
  */
 public interface PreSelectedFormNamesService
 {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml b/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
index ca3180e..5334b34 100644
--- a/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
+++ b/tapestry-core/src/test/app1/MultiZoneUpdateInsideForm.tml
@@ -8,7 +8,8 @@
     <t:zone t:id="select1ValueZone" visible="false">Show</t:zone>
     <t:zone t:id="select2ValueZone">
       <t:label for="selectValue2"/>
-      <select t:type="Select" t:id="selectValue2" t:validate="required"/>
+      <!-- Only necessary to "lock down" the clientId to facilliate the test. -->
+      <select t:type="Select" t:id="selectValue2" clientId="selectValue2" t:validate="required"/>
     </t:zone>
     <br/>
     <input type="submit" value="Upate Form"/>

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AlertsTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AlertsTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AlertsTests.groovy
index 4cf2aa9..081445b 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AlertsTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/AlertsTests.groovy
@@ -1,5 +1,3 @@
-// Copyright 2011-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
@@ -99,9 +97,14 @@ class AlertsTests extends App1TestCase {
     void ajax_update_and_remove() {
         openLinks "Alerts Demo", "Reset Alerts Storage"
 
-        select "css=#ajax select[name=\"severity\"]", "Error"
-        select "css=#ajax select[name=\"duration\"]", "Until Dismissed"
-        type "css=#ajax input[name=\"message\"]", "ajax error until"
+        def severitySelector = "css=#ajax select[name='severity_0']"
+        def durationSelector = "css=#ajax select[name='duration_0']"
+        def messageSelector = "css=#ajax input[name='message_0']"
+        def markupSelector = "css=#ajax input[name='markup_0']"
+
+        select severitySelector, "Error"
+        select durationSelector, "Until Dismissed"
+        type messageSelector, "ajax error until"
 
         click "//input[@value='Ajax Update']"
 
@@ -116,14 +119,15 @@ class AlertsTests extends App1TestCase {
         // Check that the alert container is now empty
 
         assertText "css=$CONTAINER", ""
-        
+
         // Now with markup
         openLinks "Alerts Demo", "Reset Alerts Storage"
-        
-        select "css=#ajax select[name=\"severity\"]", "Warn"
-        select "css=#ajax select[name=\"duration\"]", "Single"
-        check "css=#ajax input[name='markup']"
-        type "css=#ajax input[name='message']", "<a><span>Markup!</span></a>"
+
+        select severitySelector, "Warn"
+        select durationSelector, "Single"
+
+        check markupSelector
+        type messageSelector, "<a><span>Markup!</span></a>"
         
         click "//input[@value='Ajax Update']"
         

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java
index ceccda0..155e755 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateInsideForm.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

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/193d022b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PerFormValidationMessageDemo.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PerFormValidationMessageDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PerFormValidationMessageDemo.java
index 633142b..03399f9 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PerFormValidationMessageDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PerFormValidationMessageDemo.java
@@ -1,5 +1,3 @@
-//  Copyright 2008 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