001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.corelib.base; 014 015import org.apache.tapestry5.*; 016import org.apache.tapestry5.annotations.*; 017import org.apache.tapestry5.beaneditor.Width; 018import org.apache.tapestry5.commons.AnnotationProvider; 019import org.apache.tapestry5.corelib.mixins.RenderDisabled; 020import org.apache.tapestry5.ioc.annotations.Inject; 021import org.apache.tapestry5.ioc.internal.util.InternalUtils; 022 023import java.lang.annotation.Annotation; 024import java.util.Locale; 025 026/** 027 * Abstract class for a variety of components that render some variation of a text field. Most of the hooks for user 028 * input validation are in this class. 029 * 030 * In particular, all subclasses support the "toclient" and "parseclient" events. These two events allow the normal 031 * {@link Translator} (specified by the translate parameter, but often automatically derived by Tapestry) to be 032 * augmented. 033 * 034 * If the component container (i.e., the page) provides an event handler method for the "toclient" event, and that 035 * handler returns a non-null string, that will be the string value sent to the client. The context passed to the event 036 * handler method is t he current value of the value parameter. 037 * 038 * Likewise, on a form submit, the "parseclient" event handler method will be passed the string provided by the client, 039 * and may provide a non-null value as the parsed value. Returning null allows the normal translator to operate. The 040 * event handler may also throw {@link org.apache.tapestry5.ValidationException}. 041 * 042 * @tapestrydoc 043 */ 044@Events( 045 {EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT}) 046public abstract class AbstractTextField extends AbstractField 047{ 048 /** 049 * The value to be read and updated. This is not necessarily a string, a translator may be provided to convert 050 * between client side and server side representations. If not bound, a default binding is made to a property of the 051 * container matching the component's id. If no such property exists, then you will see a runtime exception due to 052 * the unbound value parameter. 053 */ 054 @Parameter(required = true, principal = true, autoconnect = true) 055 private Object value; 056 057 /** 058 * The object which will perform translation between server-side and client-side representations. If not specified, 059 * a value will usually be generated based on the type of the value parameter. 060 */ 061 @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.TRANSLATE) 062 private FieldTranslator<Object> translate; 063 064 /** 065 * The object that will perform input validation (which occurs after translation). The validate binding prefix is 066 * generally used to provide this object in a declarative fashion. 067 */ 068 @Parameter(defaultPrefix = BindingConstants.VALIDATE, allowNull = false) 069 @SuppressWarnings("unchecked") 070 private FieldValidator<Object> validate; 071 072 /** 073 * Provider of annotations used for some defaults. Annotation are usually provided in terms of the value parameter 074 * (i.e., from the getter and/or setter bound to the value parameter). 075 * 076 * @see org.apache.tapestry5.beaneditor.Width 077 */ 078 @Parameter 079 private AnnotationProvider annotationProvider; 080 081 /** 082 * Defines how nulls on the server side, or sent from the client side, are treated. The selected strategy may 083 * replace the nulls with some other value. The default strategy leaves nulls alone. Another built-in strategy, 084 * zero, replaces nulls with the value 0. 085 */ 086 @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default") 087 private NullFieldStrategy nulls; 088 089 @Inject 090 private Locale locale; 091 092 @SuppressWarnings("unused") 093 @Mixin 094 private RenderDisabled renderDisabled; 095 096 /** 097 * Computes a default value for the "translate" parameter using 098 * {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String, org.apache.tapestry5.ComponentResources)} 099 * . 100 */ 101 final Binding defaultTranslate() 102 { 103 return defaultProvider.defaultTranslatorBinding("value", resources); 104 } 105 106 final AnnotationProvider defaultAnnotationProvider() 107 { 108 return new AnnotationProvider() 109 { 110 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 111 { 112 return resources.getParameterAnnotation("value", annotationClass); 113 } 114 }; 115 } 116 117 /** 118 * Computes a default value for the "validate" parameter using 119 * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}. 120 */ 121 final Binding defaultValidate() 122 { 123 return defaultProvider.defaultValidatorBinding("value", resources); 124 } 125 126 @SuppressWarnings( 127 {"unchecked"}) 128 @BeginRender 129 void begin(MarkupWriter writer) 130 { 131 String value = validationTracker.getInput(this); 132 133 // If this is a response to a form submission, and the user provided a value. 134 // then send that exact value back at them. 135 136 if (value == null) 137 { 138 // Otherwise, get the value from the parameter ... 139 // Then let the translator and or various triggered events get it into 140 // a format ready to be sent to the client. 141 142 value = fieldValidationSupport.toClient(this.value, resources, translate, nulls); 143 } 144 145 writeFieldTag(writer, value); 146 147 putPropertyNameIntoBeanValidationContext("value"); 148 149 translate.render(writer); 150 validate.render(writer); 151 152 removePropertyNameFromBeanValidationContext(); 153 154 resources.renderInformalParameters(writer); 155 156 decorateInsideField(); 157 } 158 159 /** 160 * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, <input>). The 161 * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId} 162 * properties will already have been set or updated. 163 * 164 * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for 165 * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}. 166 * 167 * @param writer 168 * markup write to send output to 169 * @param value 170 * the value (either obtained and translated from the value parameter, or obtained from the tracker) 171 */ 172 protected abstract void writeFieldTag(MarkupWriter writer, String value); 173 174 @SuppressWarnings( 175 {"unchecked"}) 176 @Override 177 protected void processSubmission(String controlName) 178 { 179 String rawValue = request.getParameter(controlName); 180 181 validationTracker.recordInput(this, rawValue); 182 183 try 184 { 185 Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls); 186 187 putPropertyNameIntoBeanValidationContext("value"); 188 189 fieldValidationSupport.validate(translated, resources, validate); 190 191 // If the value provided is blank and we're ignoring blank input (i.e. PasswordField), 192 // then don't update the value parameter. 193 194 if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue))) 195 value = translated; 196 } catch (ValidationException ex) 197 { 198 validationTracker.recordError(this, ex.getMessage()); 199 } 200 201 removePropertyNameFromBeanValidationContext(); 202 } 203 204 /** 205 * Should blank input be ignored (after validation)? This will be true for 206 * {@link org.apache.tapestry5.corelib.components.PasswordField}. 207 * @return true if blank input should be ignored, false otherwise 208 */ 209 protected boolean ignoreBlankInput() 210 { 211 return false; 212 } 213 214 @Override 215 public boolean isRequired() 216 { 217 return validate.isRequired(); 218 } 219 220 /** 221 * Looks for a {@link org.apache.tapestry5.beaneditor.Width} annotation and, if present, returns its value as a 222 * string. 223 * 224 * @return the indicated width, or null if the annotation is not present 225 */ 226 protected final String getWidth() 227 { 228 Width width = annotationProvider.getAnnotation(Width.class); 229 230 if (width == null) 231 return null; 232 233 return Integer.toString(width.value()); 234 } 235}