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, <input>). 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 }