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 }