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