001// Copyright 2006, 2007, 2008, 2009, 2011, 2012 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 015package org.apache.tapestry5.corelib.base; 016 017import org.apache.tapestry5.*; 018import org.apache.tapestry5.annotations.*; 019import org.apache.tapestry5.beaneditor.Width; 020import org.apache.tapestry5.corelib.mixins.RenderDisabled; 021import org.apache.tapestry5.ioc.AnnotationProvider; 022import org.apache.tapestry5.ioc.annotations.Inject; 023import org.apache.tapestry5.ioc.internal.util.InternalUtils; 024 025import java.lang.annotation.Annotation; 026import java.util.Locale; 027 028/** 029 * Abstract class for a variety of components that render some variation of a text field. Most of the hooks for user 030 * input validation are in this class. 031 * <p/> 032 * In particular, all subclasses support the "toclient" and "parseclient" events. These two events allow the normal 033 * {@link Translator} (specified by the translate parameter, but often automatically derived by Tapestry) to be 034 * augmented. 035 * <p/> 036 * If the component container (i.e., the page) provides an event handler method for the "toclient" event, and that 037 * handler returns a non-null string, that will be the string value sent to the client. The context passed to the event 038 * handler method is t he current value of the value parameter. 039 * <p/> 040 * Likewise, on a form submit, the "parseclient" event handler method will be passed the string provided by the client, 041 * and may provide a non-null value as the parsed value. Returning null allows the normal translator to operate. The 042 * event handler may also throw {@link org.apache.tapestry5.ValidationException}. 043 * 044 * @tapestrydoc 045 */ 046@Events( 047 {EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT}) 048public 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 @Inject 092 private Locale locale; 093 094 @SuppressWarnings("unused") 095 @Mixin 096 private RenderDisabled renderDisabled; 097 098 /** 099 * Computes a default value for the "translate" parameter using 100 * {@link org.apache.tapestry5.services.ComponentDefaultProvider#defaultTranslator(String, org.apache.tapestry5.ComponentResources)} 101 * . 102 */ 103 final Binding defaultTranslate() 104 { 105 return defaultProvider.defaultTranslatorBinding("value", resources); 106 } 107 108 final AnnotationProvider defaultAnnotationProvider() 109 { 110 return new AnnotationProvider() 111 { 112 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 113 { 114 return resources.getParameterAnnotation("value", annotationClass); 115 } 116 }; 117 } 118 119 /** 120 * Computes a default value for the "validate" parameter using 121 * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}. 122 */ 123 final Binding defaultValidate() 124 { 125 return defaultProvider.defaultValidatorBinding("value", resources); 126 } 127 128 @SuppressWarnings( 129 {"unchecked"}) 130 @BeginRender 131 void begin(MarkupWriter writer) 132 { 133 String value = validationTracker.getInput(this); 134 135 // If this is a response to a form submission, and the user provided a value. 136 // then send that exact value back at them. 137 138 if (value == null) 139 { 140 // Otherwise, get the value from the parameter ... 141 // Then let the translator and or various triggered events get it into 142 // a format ready to be sent to the client. 143 144 value = fieldValidationSupport.toClient(this.value, resources, translate, nulls); 145 } 146 147 writeFieldTag(writer, value); 148 149 putPropertyNameIntoBeanValidationContext("value"); 150 151 translate.render(writer); 152 validate.render(writer); 153 154 removePropertyNameFromBeanValidationContext(); 155 156 resources.renderInformalParameters(writer); 157 158 decorateInsideField(); 159 } 160 161 /** 162 * Invoked from {@link #begin(MarkupWriter)} to write out the element and attributes (typically, <input>). The 163 * {@linkplain AbstractField#getControlName() controlName} and {@linkplain AbstractField#getClientId() clientId} 164 * properties will already have been set or updated. 165 * <p/> 166 * Generally, the subclass will invoke {@link MarkupWriter#element(String, Object[])}, and will be responsible for 167 * including an {@link AfterRender} phase method to invoke {@link MarkupWriter#end()}. 168 * 169 * @param writer 170 * markup write to send output to 171 * @param value 172 * the value (either obtained and translated from the value parameter, or obtained from the tracker) 173 */ 174 protected abstract void writeFieldTag(MarkupWriter writer, String value); 175 176 @SuppressWarnings( 177 {"unchecked"}) 178 @Override 179 protected void processSubmission(String controlName) 180 { 181 String rawValue = request.getParameter(controlName); 182 183 validationTracker.recordInput(this, rawValue); 184 185 try 186 { 187 Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls); 188 189 putPropertyNameIntoBeanValidationContext("value"); 190 191 fieldValidationSupport.validate(translated, resources, validate); 192 193 // If the value provided is blank and we're ignoring blank input (i.e. PasswordField), 194 // then don't update the value parameter. 195 196 if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue))) 197 value = translated; 198 } catch (ValidationException ex) 199 { 200 validationTracker.recordError(this, ex.getMessage()); 201 } 202 203 removePropertyNameFromBeanValidationContext(); 204 } 205 206 /** 207 * Should blank input be ignored (after validation)? This will be true for 208 * {@link org.apache.tapestry5.corelib.components.PasswordField}. 209 */ 210 protected boolean ignoreBlankInput() 211 { 212 return false; 213 } 214 215 @Override 216 public boolean isRequired() 217 { 218 return validate.isRequired(); 219 } 220 221 /** 222 * Looks for a {@link org.apache.tapestry5.beaneditor.Width} annotation and, if present, returns its value as a 223 * string. 224 * 225 * @return the indicated width, or null if the annotation is not present 226 */ 227 protected final String getWidth() 228 { 229 Width width = annotationProvider.getAnnotation(Width.class); 230 231 if (width == null) 232 return null; 233 234 return Integer.toString(width.value()); 235 } 236}