    001    // Copyright 2006, 2007, 2008, 2009, 2011 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
    015    package org.apache.tapestry5.corelib.base;
    016
    017    import org.apache.tapestry5.*;
    018    import org.apache.tapestry5.annotations.*;
    019    import org.apache.tapestry5.beaneditor.Width;
    020    import org.apache.tapestry5.corelib.mixins.RenderDisabled;
    021    import org.apache.tapestry5.ioc.AnnotationProvider;
    022    import org.apache.tapestry5.ioc.annotations.Inject;
    023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    024    import org.apache.tapestry5.services.ComponentDefaultProvider;
    025    import org.apache.tapestry5.services.Request;
    026
    027    import java.lang.annotation.Annotation;
    028    import java.util.Locale;
    029
    030    /**
    031     * Abstract class for a variety of components that render some variation of a text field. Most of the hooks for user
    032     * input validation are in this class.
    033     * <p/>
    034     * In particular, all subclasses support the "toclient" and "parseclient" events. These two events allow the normal
    035     * {@link Translator} (specified by the translate parameter, but often automatically derived by Tapestry) to be
    036     * augmented.
    037     * <p/>
    038     * If the component container (i.e., the page) provides an event handler method for the "toclient" event, and that
    039     * handler returns a non-null string, that will be the string value sent to the client. The context passed to the event
    040     * handler method is t he current value of the value parameter.
    041     * <p/>
    042     * Likewise, on a form submit, the "parseclient" event handler method will be passed the string provided by the client,
    043     * and may provide a non-null value as the parsed value. Returning null allows the normal translator to operate. The
    044     * event handler may also throw {@link org.apache.tapestry5.ValidationException}.
    045     */
    046    @Events(
    047            {EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT})
    048    public 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)
    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        @Environmental
    092        private ValidationTracker tracker;
    093
    094        @Inject
    095        private ComponentResources resources;
    096
    097        @Inject
    098        private Locale locale;
    099
    100        @Inject
    101        private Request request;
    102
    103        @Inject
    104        private FieldValidationSupport fieldValidationSupport;
    105
    106        @SuppressWarnings("unused")
    107        @Mixin
    108        private RenderDisabled renderDisabled;
    109
    110        @Inject
    111        private ComponentDefaultProvider defaultProvider;
    112
    113        /**
    114         * Computes a default value for the "translate" parameter using
    115         * {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String, org.apache.tapestry5.ComponentResources)}
    116         * .
    117         */
    118        final Binding defaultTranslate()
    119        {
    120            return defaultProvider.defaultTranslatorBinding("value", resources);
    121        }
    122
    123        final AnnotationProvider defaultAnnotationProvider()
    124        {
    125            return new AnnotationProvider()
    126            {
    127                public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    128                {
    129                    return resources.getParameterAnnotation("value", annotationClass);
    130                }
    131            };
    132        }
    133
    134        /**
    135         * Computes a default value for the "validate" parameter using
    136         * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
    137         */
    138        final Binding defaultValidate()
    139        {
    140            return defaultProvider.defaultValidatorBinding("value", resources);
    141        }
    142
    143        @SuppressWarnings(
    144                {"unchecked"})
    145        @BeginRender
    146        void begin(MarkupWriter writer)
    147        {
    148            String value = tracker.getInput(this);
    149
    150            // If this is a response to a form submission, and the user provided a value.
    151            // then send that exact value back at them.
    152
    153            if (value == null)
    154            {
    155                // Otherwise, get the value from the parameter ...
    156                // Then let the translator and or various triggered events get it into
    157                // a format ready to be sent to the client.
    158
    159                value = fieldValidationSupport.toClient(this.value, resources, translate, nulls);
    160            }
    161
    162            writeFieldTag(writer, value);
    163
    164            putPropertyNameIntoBeanValidationContext("value");
    165
    166            translate.render(writer);
    167            validate.render(writer);
    168
    169            removePropertyNameFromBeanValidationContext();
    170
    171            resources.renderInformalParameters(writer);
    172
    173            decorateInsideField();
    174        }
    175
    176        /**
    177         * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, &lt;input&gt;). The
    178         * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId}
    179         * properties will already have been set or updated.
    180         * <p/>
    181         * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for
    182         * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}.
    183         *
    184         * @param writer markup write to send output to
    185         * @param value  the value (either obtained and translated from the value parameter, or obtained from the tracker)
    186         */
    187        protected abstract void writeFieldTag(MarkupWriter writer, String value);
    188
    189        @SuppressWarnings(
    190                {"unchecked"})
    191        @Override
    192        protected void processSubmission(String controlName)
    193        {
    194            String rawValue = request.getParameter(controlName);
    195
    196            tracker.recordInput(this, rawValue);
    197
    198            try
    199            {
    200                Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls);
    201
    202                putPropertyNameIntoBeanValidationContext("value");
    203
    204                fieldValidationSupport.validate(translated, resources, validate);
    205
    206                // If the value provided is blank and we're ignoring blank input (i.e. PasswordField),
    207                // then don't update the value parameter.
    208
    209                if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue)))
    210                    value = translated;
    211            } catch (ValidationException ex)
    212            {
    213                tracker.recordError(this, ex.getMessage());
    214            }
    215
    216            removePropertyNameFromBeanValidationContext();
    217        }
    218
    219        /**
    220         * Should blank input be ignored (after validation)? This will be true for
    221         * {@link org.apache.tapestry5.corelib.components.PasswordField}.
    222         */
    223        protected boolean ignoreBlankInput()
    224        {
    225            return false;
    226        }
    227
    228        @Override
    229        public boolean isRequired()
    230        {
    231            return validate.isRequired();
    232        }
    233
    234        /**
    235         * Looks for a {@link org.apache.tapestry5.beaneditor.Width} annotation and, if present, returns its value as a
    236         * string.
    237         *
    238         * @return the indicated width, or null if the annotation is not present
    239         */
    240        protected final String getWidth()
    241        {
    242            Width width = annotationProvider.getAnnotation(Width.class);
    243
    244            if (width == null)
    245                return null;
    246
    247            return Integer.toString(width.value());
    248        }
    249    }