001    // Copyright 2006, 2007, 2008, 2009 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({ EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT })
047    public abstract class AbstractTextField extends AbstractField
048    {
049        /**
050         * The value to be read and updated. This is not necessarily a string, a translator may be provided to convert
051         * between client side and server side representations. If not bound, a default binding is made to a property of the
052         * container matching the component's id. If no such property exists, then you will see a runtime exception due to
053         * the unbound value parameter.
054         */
055        @Parameter(required = true, principal = true)
056        private Object value;
057    
058        /**
059         * The object which will perform translation between server-side and client-side representations. If not specified,
060         * a value will usually be generated based on the type of the value parameter.
061         */
062        @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.TRANSLATE)
063        private FieldTranslator<Object> translate;
064    
065        /**
066         * The object that will perform input validation (which occurs after translation). The validate binding prefix is
067         * generally used to provide this object in a declarative fashion.
068         */
069        @Parameter(defaultPrefix = BindingConstants.VALIDATE)
070        @SuppressWarnings("unchecked")
071        private FieldValidator<Object> validate;
072    
073        /**
074         * Provider of annotations used for some defaults.  Annotation are usually provided in terms of the value parameter
075         * (i.e., from the getter and/or setter bound to the value parameter).
076         *
077         * @see org.apache.tapestry5.beaneditor.Width
078         */
079        @Parameter
080        private AnnotationProvider annotationProvider;
081    
082        /**
083         * Defines how nulls on the server side, or sent from the client side, are treated. The selected strategy may
084         * replace the nulls with some other value. The default strategy leaves nulls alone.  Another built-in strategy,
085         * zero, replaces nulls with the value 0.
086         */
087        @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default")
088        private NullFieldStrategy nulls;
089    
090        @Environmental
091        private ValidationTracker tracker;
092    
093        @Inject
094        private ComponentResources resources;
095    
096        @Inject
097        private Locale locale;
098    
099        @Inject
100        private Request request;
101    
102        @Inject
103        private FieldValidationSupport fieldValidationSupport;
104    
105        @SuppressWarnings("unused")
106        @Mixin
107        private RenderDisabled renderDisabled;
108    
109        @Inject
110        private ComponentDefaultProvider defaultProvider;
111    
112        /**
113         * Computes a default value for the "translate" parameter using {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String,
114         * org.apache.tapestry5.ComponentResources)}.
115         */
116        final Binding defaultTranslate()
117        {
118            return defaultProvider.defaultTranslatorBinding("value", resources);
119        }
120    
121        final AnnotationProvider defaultAnnotationProvider()
122        {
123            return new AnnotationProvider()
124            {
125                public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
126                {
127                    return resources.getParameterAnnotation("value", annotationClass);
128                }
129            };
130        }
131    
132        /**
133         * Computes a default value for the "validate" parameter using {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
134         */
135        final Binding defaultValidate()
136        {
137            return defaultProvider.defaultValidatorBinding("value", resources);
138        }
139    
140        /**
141         * The default value is a property of the container whose name matches the component's id. May return null if the
142         * container does not have a matching property.
143         *
144         * @deprecated Likely to be removed in the future, use {@link org.apache.tapestry5.annotations.Parameter#autoconnect()}
145         *             instead
146         */
147        final Binding defaultValue()
148        {
149            return createDefaultParameterBinding("value");
150        }
151    
152        @SuppressWarnings({ "unchecked" })
153        @BeginRender
154        void begin(MarkupWriter writer)
155        {
156            String value = tracker.getInput(this);
157    
158            // If this is a response to a form submission, and the user provided a value.
159            // then send that exact value back at them.
160    
161            if (value == null)
162            {
163                // Otherwise, get the value from the parameter ...
164                // Then let the translator and or various triggered events get it into
165                // a format ready to be sent to the client.
166    
167                value = fieldValidationSupport.toClient(this.value, resources, translate, nulls);
168            }
169    
170            writeFieldTag(writer, value);
171    
172            translate.render(writer);
173            validate.render(writer);
174    
175            resources.renderInformalParameters(writer);
176    
177            decorateInsideField();
178        }
179    
180        /**
181         * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, &lt;input&gt;). The
182         * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId}
183         * properties will already have been set or updated.
184         * <p/>
185         * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for
186         * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}.
187         *
188         * @param writer markup write to send output to
189         * @param value  the value (either obtained and translated from the value parameter, or obtained from the tracker)
190         */
191        protected abstract void writeFieldTag(MarkupWriter writer, String value);
192    
193        @SuppressWarnings({ "unchecked" })
194        @Override
195        protected void processSubmission(String elementName)
196        {
197            String rawValue = request.getParameter(elementName);
198    
199            tracker.recordInput(this, rawValue);
200    
201            try
202            {
203                Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls);
204    
205                fieldValidationSupport.validate(translated, resources, validate);
206    
207                // If the value provided is blank and we're ignoring blank input (i.e. PasswordField),
208                // then don't update the value parameter.
209    
210                if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue)))
211                    value = translated;
212            }
213            catch (ValidationException ex)
214            {
215                tracker.recordError(this, ex.getMessage());
216            }
217        }
218    
219        /**
220         * Should blank input be ignored (after validation)?  This will be true for {@link
221         * 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) return null;
245    
246            return Integer.toString(width.value());
247        }
248    }