    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}