001// Copyright 2006, 2007, 2008, 2009, 2011, 2012 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.corelib.base;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.*;
019import org.apache.tapestry5.beaneditor.Width;
020import org.apache.tapestry5.corelib.mixins.RenderDisabled;
021import org.apache.tapestry5.ioc.AnnotationProvider;
022import org.apache.tapestry5.ioc.annotations.Inject;
023import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024
025import java.lang.annotation.Annotation;
026import java.util.Locale;
027
028/**
029 * Abstract class for a variety of components that render some variation of a text field. Most of the hooks for user
030 * input validation are in this class.
031 * <p/>
032 * In particular, all subclasses support the "toclient" and "parseclient" events. These two events allow the normal
033 * {@link Translator} (specified by the translate parameter, but often automatically derived by Tapestry) to be
034 * augmented.
035 * <p/>
036 * If the component container (i.e., the page) provides an event handler method for the "toclient" event, and that
037 * handler returns a non-null string, that will be the string value sent to the client. The context passed to the event
038 * handler method is t he current value of the value parameter.
039 * <p/>
040 * Likewise, on a form submit, the "parseclient" event handler method will be passed the string provided by the client,
041 * and may provide a non-null value as the parsed value. Returning null allows the normal translator to operate. The
042 * event handler may also throw {@link org.apache.tapestry5.ValidationException}.
043 *
044 * @tapestrydoc
045 */
046@Events(
047        {EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT})
048public abstract class AbstractTextField extends AbstractField
049{
050    /**
051     * The value to be read and updated. This is not necessarily a string, a translator may be provided to convert
052     * between client side and server side representations. If not bound, a default binding is made to a property of the
053     * container matching the component's id. If no such property exists, then you will see a runtime exception due to
054     * the unbound value parameter.
055     */
056    @Parameter(required = true, principal = true, autoconnect = true)
057    private Object value;
058
059    /**
060     * The object which will perform translation between server-side and client-side representations. If not specified,
061     * a value will usually be generated based on the type of the value parameter.
062     */
063    @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.TRANSLATE)
064    private FieldTranslator<Object> translate;
065
066    /**
067     * The object that will perform input validation (which occurs after translation). The validate binding prefix is
068     * generally used to provide this object in a declarative fashion.
069     */
070    @Parameter(defaultPrefix = BindingConstants.VALIDATE, allowNull = false)
071    @SuppressWarnings("unchecked")
072    private FieldValidator<Object> validate;
073
074    /**
075     * Provider of annotations used for some defaults. Annotation are usually provided in terms of the value parameter
076     * (i.e., from the getter and/or setter bound to the value parameter).
077     *
078     * @see org.apache.tapestry5.beaneditor.Width
079     */
080    @Parameter
081    private AnnotationProvider annotationProvider;
082
083    /**
084     * Defines how nulls on the server side, or sent from the client side, are treated. The selected strategy may
085     * replace the nulls with some other value. The default strategy leaves nulls alone. Another built-in strategy,
086     * zero, replaces nulls with the value 0.
087     */
088    @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default")
089    private NullFieldStrategy nulls;
090
091    @Inject
092    private Locale locale;
093
094    @SuppressWarnings("unused")
095    @Mixin
096    private RenderDisabled renderDisabled;
097
098    /**
099     * Computes a default value for the "translate" parameter using
100     * {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String, org.apache.tapestry5.ComponentResources)}
101     * .
102     */
103    final Binding defaultTranslate()
104    {
105        return defaultProvider.defaultTranslatorBinding("value", resources);
106    }
107
108    final AnnotationProvider defaultAnnotationProvider()
109    {
110        return new AnnotationProvider()
111        {
112            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
113            {
114                return resources.getParameterAnnotation("value", annotationClass);
115            }
116        };
117    }
118
119    /**
120     * Computes a default value for the "validate" parameter using
121     * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
122     */
123    final Binding defaultValidate()
124    {
125        return defaultProvider.defaultValidatorBinding("value", resources);
126    }
127
128    @SuppressWarnings(
129            {"unchecked"})
130    @BeginRender
131    void begin(MarkupWriter writer)
132    {
133        String value = validationTracker.getInput(this);
134
135        // If this is a response to a form submission, and the user provided a value.
136        // then send that exact value back at them.
137
138        if (value == null)
139        {
140            // Otherwise, get the value from the parameter ...
141            // Then let the translator and or various triggered events get it into
142            // a format ready to be sent to the client.
143
144            value = fieldValidationSupport.toClient(this.value, resources, translate, nulls);
145        }
146
147        writeFieldTag(writer, value);
148
149        putPropertyNameIntoBeanValidationContext("value");
150
151        translate.render(writer);
152        validate.render(writer);
153
154        removePropertyNameFromBeanValidationContext();
155
156        resources.renderInformalParameters(writer);
157
158        decorateInsideField();
159    }
160
161    /**
162     * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, &lt;input&gt;). The
163     * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId}
164     * properties will already have been set or updated.
165     * <p/>
166     * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for
167     * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}.
168     *
169     * @param writer
170     *         markup write to send output to
171     * @param value
172     *         the value (either obtained and translated from the value parameter, or obtained from the tracker)
173     */
174    protected abstract void writeFieldTag(MarkupWriter writer, String value);
175
176    @SuppressWarnings(
177            {"unchecked"})
178    @Override
179    protected void processSubmission(String controlName)
180    {
181        String rawValue = request.getParameter(controlName);
182
183        validationTracker.recordInput(this, rawValue);
184
185        try
186        {
187            Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls);
188
189            putPropertyNameIntoBeanValidationContext("value");
190
191            fieldValidationSupport.validate(translated, resources, validate);
192
193            // If the value provided is blank and we're ignoring blank input (i.e. PasswordField),
194            // then don't update the value parameter.
195
196            if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue)))
197                value = translated;
198        } catch (ValidationException ex)
199        {
200            validationTracker.recordError(this, ex.getMessage());
201        }
202
203        removePropertyNameFromBeanValidationContext();
204    }
205
206    /**
207     * Should blank input be ignored (after validation)? This will be true for
208     * {@link org.apache.tapestry5.corelib.components.PasswordField}.
209     */
210    protected boolean ignoreBlankInput()
211    {
212        return false;
213    }
214
215    @Override
216    public boolean isRequired()
217    {
218        return validate.isRequired();
219    }
220
221    /**
222     * Looks for a {@link org.apache.tapestry5.beaneditor.Width} annotation and, if present, returns its value as a
223     * string.
224     *
225     * @return the indicated width, or null if the annotation is not present
226     */
227    protected final String getWidth()
228    {
229        Width width = annotationProvider.getAnnotation(Width.class);
230
231        if (width == null)
232            return null;
233
234        return Integer.toString(width.value());
235    }
236}