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 2008/01/19 01:27:23 UTC

svn commit: r613323 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry/corelib/base/ main/java/org/apache/tapestry/corelib/components/ main/java/org/apache/tapestry/internal/util/ main/java/org/apache/tapestry/test/ test/ja...

Author: hlship
Date: Fri Jan 18 16:27:22 2008
New Revision: 613323

URL: http://svn.apache.org/viewvc?rev=613323&view=rev
Log:
TAPESTRY-1941: ValidationTracker retaining field values inconsistently

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/corelib/components/current_selection_from_validation_tracker.txt
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Checkbox.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/DateField.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Radio.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/RadioGroup.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Select.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/util/SelectModelRenderer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/corelib/components/SelectTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java Fri Jan 18 16:27:22 2008
@@ -26,25 +26,23 @@
 import java.io.Serializable;
 
 /**
- * Provides initialization of the clientId and elementName properties. In addition, adds the
- * {@link RenderInformals}, {@link RenderDisabled} and {@link DiscardBody} mixins.
+ * Provides initialization of the clientId and elementName properties. In addition, adds the {@link RenderInformals},
+ * {@link RenderDisabled} and {@link DiscardBody} mixins.
  */
 @SupportsInformalParameters
 public abstract class AbstractField implements Field
 {
     /**
-     * The user presentable label for the field. If not provided, a reasonable label is generated
-     * from the component's id, first by looking for a message key named "id-label" (substituting
-     * the component's actual id), then by converting the actual id to a presentable string (for
-     * example, "userId" to "User Id").
+     * The user presentable label for the field. If not provided, a reasonable label is generated from the component's
+     * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
+     * converting the actual id to a presentable string (for example, "userId" to "User Id").
      */
     @Parameter(defaultPrefix = "literal")
     private String _label;
 
     /**
-     * If true, then the field will render out with a disabled attribute (to turn off client-side
-     * behavior). Further, a disabled field ignores any value in the request when the form is
-     * submitted.
+     * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
+     * disabled field ignores any value in the request when the form is submitted.
      */
     @Parameter("false")
     private boolean _disabled;
@@ -101,12 +99,11 @@
     private static final ProcessSubmissionAction PROCESS_SUBMISSION_ACTION = new ProcessSubmissionAction();
 
     /**
-     * The id used to generate a page-unique client-side identifier for the component. If a
-     * component renders multiple times, a suffix will be appended to the to id to ensure
-     * uniqueness. The uniqued value may be accessed via the
+     * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
+     * times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the
      * {@link #getClientId() clientId property}.
      */
-    @Parameter(value = "prop:componentResources.id", defaultPrefix = "literal")
+    @Parameter(value = "prop:componentResources.id", defaultPrefix = TapestryConstants.LITERAL_BINDING_PREFIX)
     private String _clientId;
 
     private String _assignedClientId;
@@ -183,11 +180,9 @@
     }
 
     /**
-     * Used by subclasses to create a default binding to a property of the container matching the
-     * component id.
+     * Used by subclasses to create a default binding to a property of the container matching the component id.
      *
-     * @return a binding to the property, or null if the container does not have a corresponding
-     *         property
+     * @return a binding to the property, or null if the container does not have a corresponding property
      */
     protected final Binding createDefaultParameterBinding(String parameterName)
     {
@@ -195,9 +190,9 @@
     }
 
     /**
-     * Method implemented by subclasses to actually do the work of processing the submission of the
-     * form. The element's elementName property will already have been set. This method is only
-     * invoked if the field is <strong>not {@link #isDisabled() disabled}</strong>.
+     * Method implemented by subclasses to actually do the work of processing the submission of the form. The element's
+     * elementName property will already have been set. This method is only invoked if the field is <strong>not {@link
+     * #isDisabled() disabled}</strong>.
      *
      * @param elementName the name of the element (used to find the correct parameter in the request)
      */
@@ -213,8 +208,7 @@
     }
 
     /**
-     * Allows the validation decorator to write markup after the field has written all of its
-     * markup.
+     * Allows the validation decorator to write markup after the field has written all of its markup.
      */
     @AfterRender
     final void afterDecorator()
@@ -223,9 +217,8 @@
     }
 
     /**
-     * Invoked from subclasses after they have written their tag and (where appropriate) their
-     * informal parameters <em>and</em> have allowed their {@link Validator} to write markup as
-     * well.
+     * Invoked from subclasses after they have written their tag and (where appropriate) their informal parameters
+     * <em>and</em> have allowed their {@link Validator} to write markup as well.
      */
     protected final void decorateInsideField()
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Checkbox.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Checkbox.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Checkbox.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Checkbox.java Fri Jan 18 16:27:22 2008
@@ -17,10 +17,8 @@
 import org.apache.tapestry.Binding;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.annotations.AfterRender;
-import org.apache.tapestry.annotations.BeginRender;
-import org.apache.tapestry.annotations.Mixin;
-import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.ValidationTracker;
+import org.apache.tapestry.annotations.*;
 import org.apache.tapestry.corelib.base.AbstractField;
 import org.apache.tapestry.corelib.mixins.RenderDisabled;
 import org.apache.tapestry.ioc.annotations.Inject;
@@ -39,8 +37,8 @@
     private RenderDisabled _renderDisabled;
 
     /**
-     * The value to be read or updated. If not bound, the Checkbox will attempt to edit a property
-     * of its container whose name matches the component's id.
+     * The value to be read or updated. If not bound, the Checkbox will attempt to edit a property of its container
+     * whose name matches the component's id.
      */
     @Parameter(required = true)
     private boolean _value;
@@ -48,6 +46,9 @@
     @Inject
     private ComponentResources _resources;
 
+    @Environmental
+    private ValidationTracker _tracker;
+
     Binding defaultValue()
     {
         return createDefaultParameterBinding("value");
@@ -56,13 +57,17 @@
     @BeginRender
     void begin(MarkupWriter writer)
     {
+        String asSubmitted = _tracker.getInput(this);
+
+        boolean checked = asSubmitted != null ? Boolean.parseBoolean(asSubmitted) : _value;
+
         writer.element("input", "type", "checkbox",
 
                        "name", getElementName(),
 
                        "id", getClientId(),
 
-                       "checked", _value ? "checked" : null);
+                       "checked", checked ? "checked" : null);
 
         _resources.renderInformalParameters(writer);
 
@@ -79,6 +84,10 @@
     protected void processSubmission(String elementName)
     {
         String postedValue = _request.getParameter(elementName);
+
+        // record as "true" or "false"
+
+        _tracker.recordInput(this, Boolean.toString(postedValue != null));
 
         _value = postedValue != null;
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/DateField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/DateField.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/DateField.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/DateField.java Fri Jan 18 16:27:22 2008
@@ -49,8 +49,8 @@
     private String _format = "%m/%d/%y";
 
     /**
-     * The object that will perform input validation (which occurs after translation). The translate
-     * binding prefix is generally used to provide this object in a declarative fashion.
+     * The object that will perform input validation (which occurs after translation). The translate binding prefix is
+     * generally used to provide this object in a declarative fashion.
      */
     @Parameter(defaultPrefix = "validate")
     @SuppressWarnings("unchecked")
@@ -58,8 +58,8 @@
 
 
     /**
-     * If true, then the client-side calendar will show the time as well as the date.  You will probably
-     * need to bind the format parameter as well when this is true, say to <code>%m/%d/%y %H:%M</code>.
+     * If true, then the client-side calendar will show the time as well as the date.  You will probably need to bind
+     * the format parameter as well when this is true, say to <code>%m/%d/%y %H:%M</code>.
      */
     @Parameter
     private boolean _editTime;
@@ -113,9 +113,12 @@
     @Inject
     private FieldValidatorDefaultSource _fieldValidatorDefaultSource;
 
+    @Inject
+    private FieldValidationSupport _fieldValidationSupport;
+
     /**
-     * The default value is a property of the container whose name matches the component's id. May
-     * return null if the container does not have a matching property.
+     * The default value is a property of the container whose name matches the component's id. May return null if the
+     * container does not have a matching property.
      */
     final Binding defaultValue()
     {
@@ -123,8 +126,7 @@
     }
 
     /**
-     * Computes a default value for the "validate" parameter using
-     * {@link FieldValidatorDefaultSource}.
+     * Computes a default value for the "validate" parameter using {@link FieldValidatorDefaultSource}.
      */
     final FieldValidator defaultValidate()
     {
@@ -212,10 +214,9 @@
     }
 
     /**
-     * Invoked to allow subclasses to further configure the parameters passed to the JavaScript
-     * Calendar.setup() function. The values inputField, ifFormat and button are pre-configured.
-     * Subclasses may override this method to configure additional features of the client-side
-     * Calendar. This implementation does nothing.
+     * Invoked to allow subclasses to further configure the parameters passed to the JavaScript Calendar.setup()
+     * function. The values inputField, ifFormat and button are pre-configured. Subclasses may override this method to
+     * configure additional features of the client-side Calendar. This implementation does nothing.
      *
      * @param setup parameters object
      */
@@ -224,7 +225,7 @@
 
     }
 
-    String formatCurrentValue()
+    private String formatCurrentValue()
     {
         if (_value == null) return "";
 
@@ -234,26 +235,33 @@
     @Override
     protected void processSubmission(String elementName)
     {
-        // TODO: Validation
-
         String value = _request.getParameter(elementName);
 
-        if (InternalUtils.isBlank(value))
-        {
-            _value = null;
-            return;
-        }
+        _tracker.recordInput(this, value);
+
+        Date parsedValue = null;
 
         try
         {
-            _value = toJavaDateFormat().parse(value);
+            if (InternalUtils.isNonBlank(value)) parsedValue = toJavaDateFormat().parse(value);
 
         }
         catch (ParseException ex)
         {
             _tracker.recordError(this, "Date value is not parseable.");
+            return;
         }
 
+        try
+        {
+            _fieldValidationSupport.validate(parsedValue, _resources, _validate);
+
+            _value = parsedValue;
+        }
+        catch (ValidationException ex)
+        {
+            _tracker.recordError(this, ex.getMessage());
+        }
     }
 
     SimpleDateFormat toJavaDateFormat()

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Radio.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Radio.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Radio.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Radio.java Fri Jan 18 16:27:22 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 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.
@@ -25,11 +25,11 @@
 import org.apache.tapestry.services.ComponentDefaultProvider;
 
 /**
- * A radio button (i.e., &lt;input type="radio"&gt;). Radio buttons <strong>must</strong> operate
- * within a {@link RadioContainer} (normally, the {@link RadioGroup} component).
+ * A radio button (i.e., &lt;input type="radio"&gt;). Radio buttons <strong>must</strong> operate within a {@link
+ * RadioContainer} (normally, the {@link RadioGroup} component).
  * <p/>
- * If the value parameter is not bound, then the default value is a property of the container
- * component whose name matches the Radio component's id.
+ * If the value parameter is not bound, then the default value is a property of the container component whose name
+ * matches the Radio component's id.
  */
 public class Radio implements Field
 {
@@ -37,18 +37,16 @@
     private RadioContainer _container;
 
     /**
-     * The user presentable label for the field. If not provided, a reasonable label is generated
-     * from the component's id, first by looking for a message key named "id-label" (substituting
-     * the component's actual id), then by converting the actual id to a presentable string (for
-     * example, "userId" to "User Id").
+     * The user presentable label for the field. If not provided, a reasonable label is generated from the component's
+     * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
+     * converting the actual id to a presentable string (for example, "userId" to "User Id").
      */
-    @Parameter(defaultPrefix = "literal")
+    @Parameter(defaultPrefix = TapestryConstants.LITERAL_BINDING_PREFIX)
     private String _label;
 
     /**
-     * The value associated with this radio button. This is used to determine which radio button
-     * will be selected when the page is rendered, and also becomes the value assigned when the form
-     * is submitted.
+     * The value associated with this radio button. This is used to determine which radio button will be selected when
+     * the page is rendered, and also becomes the value assigned when the form is submitted.
      */
     @Parameter(required = true, principal = true)
     private Object _value;
@@ -79,9 +77,8 @@
     private String _elementName;
 
     /**
-     * If true, then the field will render out with a disabled attribute (to turn off client-side
-     * behavior). Further, a disabled field ignores any value in the request when the form is
-     * submitted.
+     * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
+     * disabled field ignores any value in the request when the form is submitted.
      */
     @Parameter("false")
     private boolean _disabled;
@@ -107,8 +104,8 @@
     }
 
     /**
-     * Returns true if this component has been expressly disabled (via its disabled parameter), or
-     * if the {@link RadioContainer container} has been disabled.
+     * Returns true if this component has been expressly disabled (via its disabled parameter), or if the {@link
+     * RadioContainer container} has been disabled.
      */
     public boolean isDisabled()
     {
@@ -127,16 +124,7 @@
         _clientId = _pageRenderSupport.allocateClientId(_resources.getId());
         _elementName = _container.getElementName();
 
-        writer.element(
-                "input",
-                "type",
-                "radio",
-                "id",
-                _clientId,
-                "name",
-                _elementName,
-                "value",
-                value);
+        writer.element("input", "type", "radio", "id", _clientId, "name", _elementName, "value", value);
 
         if (_container.isSelected(_value)) writer.attributes("checked", "checked");
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/RadioGroup.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/RadioGroup.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/RadioGroup.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/RadioGroup.java Fri Jan 18 16:27:22 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 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.
@@ -21,7 +21,7 @@
 import org.apache.tapestry.ioc.annotations.Inject;
 import org.apache.tapestry.services.*;
 
-public class RadioGroup
+public class RadioGroup implements Field
 {
     /**
      * The property read and updated by the group as a whole.
@@ -30,16 +30,15 @@
     private Object _value;
 
     /**
-     * If true, then the field will render out with a disabled attribute (to turn off client-side
-     * behavior). Further, a disabled field ignores any value in the request when the form is
-     * submitted.
+     * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
+     * disabled field ignores any value in the request when the form is submitted.
      */
     @Parameter("false")
     private boolean _disabled;
 
     /**
-     * Allows a specific implementation of {@link ValueEncoder} to be supplied. This is used to
-     * create client-side string values for the different radio button values.
+     * Allows a specific implementation of {@link ValueEncoder} to be supplied. This is used to create client-side
+     * string values for the different radio button values.
      *
      * @see ValueEncoderSource
      */
@@ -64,6 +63,9 @@
     @Inject
     private Request _request;
 
+    @Environmental
+    private ValidationTracker _tracker;
+
     private String _elementName;
 
     final Binding defaultValue()
@@ -112,12 +114,14 @@
     {
         String clientValue = _request.getParameter(_elementName);
 
+        _tracker.recordInput(this, clientValue);
+
         _value = _encoder.toValue(clientValue);
     }
 
     /**
-     * Obtains the element name for the group, and stores a {@link RadioContainer} into the
-     * {@link Environment} (so that the {@link Radio} components can find it).
+     * Obtains the element name for the group, and stores a {@link RadioContainer} into the {@link Environment} (so that
+     * the {@link Radio} components can find it).
      */
     final void setupRender()
     {
@@ -127,6 +131,11 @@
 
         _formSupport.storeAndExecute(this, action);
 
+        String submittedValue = _tracker.getInput(this);
+
+        final String selectedValue = submittedValue != null ? submittedValue : _encoder.toClient(_value);
+
+
         _environment.push(RadioContainer.class, new RadioContainer()
         {
             public String getElementName()
@@ -149,7 +158,7 @@
 
             public boolean isSelected(Object value)
             {
-                return TapestryInternalUtils.isEqual(value, _value);
+                return TapestryInternalUtils.isEqual(_encoder.toClient(value), selectedValue);
             }
 
         });
@@ -165,4 +174,33 @@
         _environment.pop(RadioContainer.class);
     }
 
+    public String getElementName()
+    {
+        return _elementName;
+    }
+
+    /**
+     * Always returns null; individual {@link org.apache.tapestry.corelib.components.Radio} components may have their
+     * own label.
+     */
+    public String getLabel()
+    {
+        return null;
+    }
+
+    public boolean isDisabled()
+    {
+        return _disabled;
+    }
+
+    /**
+     * Returns null; the radio group does not render as a tag and so doesn't have an id to share.  RadioGroup implements
+     * {@link org.apache.tapestry.Field} only so it can interact with the {@link org.apache.tapestry.ValidationTracker}.
+     *
+     * @return null
+     */
+    public String getClientId()
+    {
+        return null;
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Select.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Select.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Select.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Select.java Fri Jan 18 16:27:22 2008
@@ -21,23 +21,26 @@
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.corelib.base.AbstractField;
 import org.apache.tapestry.corelib.mixins.RenderDisabled;
+import org.apache.tapestry.internal.TapestryInternalUtils;
 import org.apache.tapestry.internal.util.SelectModelRenderer;
 import org.apache.tapestry.ioc.annotations.Inject;
-import org.apache.tapestry.services.*;
+import org.apache.tapestry.services.FieldValidatorDefaultSource;
+import org.apache.tapestry.services.Request;
+import org.apache.tapestry.services.ValueEncoderFactory;
+import org.apache.tapestry.services.ValueEncoderSource;
 import org.apache.tapestry.util.EnumSelectModel;
 
 import java.util.Locale;
 
 /**
- * Select an item from a list of values, using an [X]HTML &lt;select&gt; element on the client side.
- * An validation decorations will go around the entire &lt;select&gt; element.
+ * Select an item from a list of values, using an [X]HTML &lt;select&gt; element on the client side. An validation
+ * decorations will go around the entire &lt;select&gt; element.
  * <p/>
- * A core part of this component is the {@link ValueEncoder} (the encoder parameter) that is used to
- * convert between server-side values and client-side strings. In many cases, a {@link ValueEncoder}
- * can be generated automatically from the type of the value parameter. The
- * {@link ValueEncoderSource} service provides an encoder in these situations; it can be overriden
- * by binding the encoder parameter, or extended by contributing a {@link ValueEncoderFactory} into
- * the service's configuration.
+ * A core part of this component is the {@link ValueEncoder} (the encoder parameter) that is used to convert between
+ * server-side values and client-side strings. In many cases, a {@link ValueEncoder} can be generated automatically from
+ * the type of the value parameter. The {@link ValueEncoderSource} service provides an encoder in these situations; it
+ * can be overriden by binding the encoder parameter, or extended by contributing a {@link ValueEncoderFactory} into the
+ * service's configuration.
  */
 public final class Select extends AbstractField
 {
@@ -50,17 +53,15 @@
         }
 
         @Override
-        protected boolean isOptionSelected(OptionModel optionModel)
+        protected boolean isOptionSelected(OptionModel optionModel, String clientValue)
         {
-            Object value = optionModel.getValue();
-
-            return value == _value || (value != null && value.equals(_value));
+            return isSelected(clientValue);
         }
     }
 
     /**
-     * Allows a specific implementation of {@link ValueEncoder} to be supplied. This is used to
-     * create client-side string values for the different options.
+     * Allows a specific implementation of {@link ValueEncoder} to be supplied. This is used to create client-side
+     * string values for the different options.
      *
      * @see ValueEncoderSource
      */
@@ -75,8 +76,8 @@
 
     // Maybe this should default to property "<componentId>Model"?
     /**
-     * The model used to identify the option groups and options to be presented to the user. This
-     * can be generated automatically for Enum types.
+     * The model used to identify the option groups and options to be presented to the user. This can be generated
+     * automatically for Enum types.
      */
     @Parameter(required = true)
     private SelectModel _model;
@@ -113,13 +114,22 @@
     @Mixin
     private RenderDisabled _renderDisabled;
 
+    private String _selectedClientValue;
+
+    private boolean isSelected(String clientValue)
+    {
+        return TapestryInternalUtils.isEqual(clientValue, _selectedClientValue);
+    }
+
     @SuppressWarnings({"unchecked"})
     @Override
     protected void processSubmission(String elementName)
     {
-        String primaryKey = _request.getParameter(elementName);
+        String submittedValue = _request.getParameter(elementName);
+
+        _tracker.recordInput(this, submittedValue);
 
-        Object selectedValue = _encoder.toValue(primaryKey);
+        Object selectedValue = _encoder.toValue(submittedValue);
 
         try
         {
@@ -145,6 +155,9 @@
         _resources.renderInformalParameters(writer);
 
         // Disabled via mixin
+
+        // Figure out
+
     }
 
     @SuppressWarnings("unchecked")
@@ -167,8 +180,7 @@
     }
 
     /**
-     * Computes a default value for the "validate" parameter using
-     * {@link FieldValidatorDefaultSource}.
+     * Computes a default value for the "validate" parameter using {@link FieldValidatorDefaultSource}.
      */
     FieldValidator defaultValidate()
     {
@@ -189,6 +201,14 @@
     @BeforeRenderTemplate
     void options(MarkupWriter writer)
     {
+        _selectedClientValue = _tracker.getInput(this);
+
+        // Use the value passed up in the form submission, if available.
+        // Failing that, see if there is a current value (via the value parameter), and
+        // convert that to a client value for later comparison.
+
+        if (_selectedClientValue == null) _selectedClientValue = _value == null ? null : _encoder.toClient(_value);
+
         SelectModelVisitor renderer = new Renderer(writer);
 
         _model.visit(renderer);
@@ -209,5 +229,10 @@
     void setValueEncoder(ValueEncoder encoder)
     {
         _encoder = encoder;
+    }
+
+    void setValidationTracker(ValidationTracker tracker)
+    {
+        _tracker = tracker;
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/util/SelectModelRenderer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/util/SelectModelRenderer.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/util/SelectModelRenderer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/util/SelectModelRenderer.java Fri Jan 18 16:27:22 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 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.
@@ -52,7 +52,7 @@
 
         _writer.element("option", "value", clientValue);
 
-        if (isOptionSelected(optionModel)) _writer.attributes("selected", "selected");
+        if (isOptionSelected(optionModel, clientValue)) _writer.attributes("selected", "selected");
 
         writeDisabled(optionModel.isDisabled());
         writeAttributes(optionModel.getAttributes());
@@ -76,10 +76,9 @@
     }
 
     /**
-     * If true, then the selected attribute will be written. This implementation always returns
-     * false.
+     * If true, then the selected attribute will be written. This implementation always returns false.
      */
-    protected boolean isOptionSelected(OptionModel optionModel)
+    protected boolean isOptionSelected(OptionModel optionModel, String clientValue)
     {
         return false;
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java Fri Jan 18 16:27:22 2008
@@ -974,4 +974,9 @@
     {
         expect(manager.get(asoClass)).andReturn(aso);
     }
+
+    protected final void train_getInput(ValidationTracker tracker, Field field, String input)
+    {
+        expect(tracker.getInput(field)).andReturn(input);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/corelib/components/SelectTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/corelib/components/SelectTest.java?rev=613323&r1=613322&r2=613323&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/corelib/components/SelectTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/corelib/components/SelectTest.java Fri Jan 18 16:27:22 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 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.
@@ -14,10 +14,7 @@
 
 package org.apache.tapestry.corelib.components;
 
-import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.OptionGroupModel;
-import org.apache.tapestry.OptionModel;
-import org.apache.tapestry.SelectModel;
+import org.apache.tapestry.*;
 import org.apache.tapestry.dom.XMLMarkupModel;
 import org.apache.tapestry.internal.OptionGroupModelImpl;
 import org.apache.tapestry.internal.OptionModelImpl;
@@ -39,8 +36,8 @@
 import java.util.Map;
 
 /**
- * Mostly, this is about how the Select component renders its {@link SelectModel}. The real nuts
- * and bolts are tested in the integration tests.
+ * Mostly, this is about how the Select component renders its {@link SelectModel}. The real nuts and bolts are tested in
+ * the integration tests.
  */
 public class SelectTest extends InternalBaseTestCase
 {
@@ -48,11 +45,21 @@
     @Test
     public void empty_model()
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
+
         select.setModel(new SelectModelImpl(null, null));
+        select.setValidationTracker(tracker);
 
         select.options(null);
+
+        verify();
     }
 
     private String read(String file) throws Exception
@@ -80,14 +87,22 @@
     @Test
     public void just_options() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         List<OptionModel> options = TapestryInternalUtils
                 .toOptionModels("fred=Fred Flintstone,barney=Barney Rubble");
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
+
         select.setModel(new SelectModelImpl(null, options));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("barney");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -98,11 +113,51 @@
         writer.end();
 
         assertEquals(writer.toString(), read("just_options.txt"));
+
+        verify();
+    }
+
+    @Test
+    public void current_selection_from_validation_tracker() throws Exception
+    {
+        ValidationTracker tracker = mockValidationTracker();
+
+        List<OptionModel> options = TapestryInternalUtils
+                .toOptionModels("fred=Fred Flintstone,barney=Barney Rubble");
+
+        Select select = new Select();
+
+        train_getInput(tracker, select, "fred");
+
+        replay();
+
+
+        select.setModel(new SelectModelImpl(null, options));
+        select.setValueEncoder(new StringValueEncoder());
+        select.setValue("barney");
+        select.setValidationTracker(tracker);
+
+        MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
+
+        writer.element("select");
+
+        select.options(writer);
+
+        writer.end();
+
+        // fred will be selected, not barney, because the validation tracker
+        // takes precendence.
+
+        assertEquals(writer.toString(), read("current_selection_from_validation_tracker.txt"));
+
+        verify();
     }
 
     @Test
     public void option_attributes() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         // Extra cast needed for Sun compiler, not Eclipse compiler.
 
         List<OptionModel> options = Arrays.asList(
@@ -110,9 +165,14 @@
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
         select.setModel(new SelectModelImpl(null, options));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("barney");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -123,11 +183,15 @@
         writer.end();
 
         assertEquals(writer.toString(), read("option_attributes.txt"));
+
+        verify();
     }
 
     @Test
     public void disabled_option() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         // Extra cast needed for Sun compiler, not Eclipse compiler.
 
         List<OptionModel> options = CollectionFactory.newList(
@@ -135,9 +199,14 @@
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
         select.setModel(new SelectModelImpl(null, options));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("barney");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -149,11 +218,14 @@
 
         assertEquals(writer.toString(), read("disabled_option.txt"));
 
+        verify();
     }
 
     @Test
     public void option_groups() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         OptionGroupModel husbands = new OptionGroupModelImpl("Husbands", false,
                                                              TapestryInternalUtils.toOptionModels("Fred,Barney"));
         OptionGroupModel wives = new OptionGroupModelImpl("Wives", true, TapestryInternalUtils
@@ -162,9 +234,14 @@
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
         select.setModel(new SelectModelImpl(groupModels, null));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("Fred");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -175,20 +252,29 @@
         writer.end();
 
         assertEquals(writer.toString(), read("option_groups.txt"));
+
+        verify();
     }
 
     @Test
     public void option_groups_precede_ungroup_options() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         OptionGroupModel husbands = new OptionGroupModelImpl("Husbands", false,
                                                              TapestryInternalUtils.toOptionModels("Fred,Barney"));
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
         select.setModel(new SelectModelImpl(Collections.singletonList(husbands),
                                             TapestryInternalUtils.toOptionModels("Wilma,Betty")));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("Fred");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -199,11 +285,15 @@
         writer.end();
 
         assertEquals(writer.toString(), read("option_groups_precede_ungroup_options.txt"));
+
+        verify();
     }
 
     @Test
     public void option_group_attributes() throws Exception
     {
+        ValidationTracker tracker = mockValidationTracker();
+
         Map<String, String> attributes = Collections.singletonMap("class", "pixie");
 
         OptionGroupModel husbands = new OptionGroupModelImpl("Husbands", false,
@@ -212,9 +302,14 @@
 
         Select select = new Select();
 
+        train_getInput(tracker, select, null);
+
+        replay();
+
         select.setModel(new SelectModelImpl(Collections.singletonList(husbands), null));
         select.setValueEncoder(new StringValueEncoder());
         select.setValue("Fred");
+        select.setValidationTracker(tracker);
 
         MarkupWriter writer = new MarkupWriterImpl(new XMLMarkupModel());
 
@@ -225,5 +320,7 @@
         writer.end();
 
         assertEquals(writer.toString(), read("option_group_attributes.txt"));
+
+        verify();
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/corelib/components/current_selection_from_validation_tracker.txt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/corelib/components/current_selection_from_validation_tracker.txt?rev=613323&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/corelib/components/current_selection_from_validation_tracker.txt (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/corelib/components/current_selection_from_validation_tracker.txt Fri Jan 18 16:27:22 2008
@@ -0,0 +1,2 @@
+<?xml version="1.0"?>
+<select><option selected="selected" value="fred">Fred Flintstone</option><option value="barney">Barney Rubble</option></select>
\ No newline at end of file