001 // Copyright 2004, 2005 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.tapestry.valid; 016 017 import java.util.ArrayList; 018 import java.util.Collections; 019 import java.util.HashMap; 020 import java.util.Iterator; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.tapestry.IMarkupWriter; 025 import org.apache.tapestry.IRender; 026 import org.apache.tapestry.IRequestCycle; 027 import org.apache.tapestry.Tapestry; 028 import org.apache.tapestry.form.IFormComponent; 029 030 /** 031 * A base implementation of {@link IValidationDelegate}that can be used as a managed bean. This 032 * class is often subclassed, typically to override presentation details. 033 * 034 * @author Howard Lewis Ship 035 * @since 1.0.5 036 */ 037 038 public class ValidationDelegate implements IValidationDelegate 039 { 040 private static final long serialVersionUID = 6215074338439140780L; 041 042 private transient IFormComponent _currentComponent; 043 044 private transient String _focusField; 045 046 private transient int _focusPriority = -1; 047 048 /** 049 * A list of {@link IFieldTracking}. 050 */ 051 052 private final List _trackings = new ArrayList(); 053 054 /** 055 * A map of {@link IFieldTracking}, keyed on form element name. 056 */ 057 058 private final Map _trackingMap = new HashMap(); 059 060 public void clear() 061 { 062 _currentComponent = null; 063 _trackings.clear(); 064 _trackingMap.clear(); 065 } 066 067 public void clearErrors() 068 { 069 if (_trackings == null) 070 return; 071 072 Iterator i = _trackings.iterator(); 073 while (i.hasNext()) 074 { 075 FieldTracking ft = (FieldTracking) i.next(); 076 ft.setErrorRenderer(null); 077 } 078 } 079 080 /** 081 * If the form component is in error, places a <font color="red"< around it. Note: this 082 * will only work on the render phase after a rewind, and will be confused if components are 083 * inside any kind of loop. 084 */ 085 086 public void writeLabelPrefix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle) 087 { 088 if (isInError(component)) 089 { 090 writer.begin("font"); 091 writer.attribute("color", "red"); 092 } 093 } 094 095 /** 096 * Closes the <font> element,started by 097 * {@link #writeLabelPrefix(IFormComponent,IMarkupWriter,IRequestCycle)}, if the form component 098 * is in error. 099 */ 100 101 public void writeLabelSuffix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle) 102 { 103 if (isInError(component)) 104 { 105 writer.end(); 106 } 107 } 108 109 /** 110 * Returns the {@link IFieldTracking}for the current component, if any. The 111 * {@link IFieldTracking}is usually created in {@link #record(String, ValidationConstraint)}or 112 * in {@link #record(IRender, ValidationConstraint)}. 113 * <p> 114 * Components may be rendered multiple times, with multiple names (provided by the 115 * {@link org.apache.tapestry.form.Form}, care must be taken that this method is invoked 116 * <em>after</em> the Form has provided a unique {@link IFormComponent#getName()}for the 117 * component. 118 * 119 * @see #setFormComponent(IFormComponent) 120 * @return the {@link FieldTracking}, or null if the field has no tracking. 121 */ 122 123 protected FieldTracking getComponentTracking() 124 { 125 return (FieldTracking) _trackingMap.get(_currentComponent.getName()); 126 } 127 128 public void setFormComponent(IFormComponent component) 129 { 130 _currentComponent = component; 131 } 132 133 public boolean isInError() 134 { 135 IFieldTracking tracking = getComponentTracking(); 136 137 return tracking != null && tracking.isInError(); 138 } 139 140 public String getFieldInputValue() 141 { 142 IFieldTracking tracking = getComponentTracking(); 143 144 return tracking == null ? null : tracking.getInput(); 145 } 146 147 /** 148 * Returns all the field trackings as an unmodifiable List. 149 */ 150 151 public List getFieldTracking() 152 { 153 if (Tapestry.size(_trackings) == 0) 154 return null; 155 156 return Collections.unmodifiableList(_trackings); 157 } 158 159 /** @since 3.0.2 */ 160 public IFieldTracking getCurrentFieldTracking() 161 { 162 return findCurrentTracking(); 163 } 164 165 public void reset() 166 { 167 IFieldTracking tracking = getComponentTracking(); 168 169 if (tracking != null) 170 { 171 _trackings.remove(tracking); 172 _trackingMap.remove(tracking.getFieldName()); 173 } 174 } 175 176 /** 177 * Invokes {@link #record(String, ValidationConstraint)}, or 178 * {@link #record(IRender, ValidationConstraint)}if the 179 * {@link ValidatorException#getErrorRenderer() error renderer property}is not null. 180 */ 181 182 public void record(ValidatorException ex) 183 { 184 IRender errorRenderer = ex.getErrorRenderer(); 185 186 if (errorRenderer == null) 187 record(ex.getMessage(), ex.getConstraint()); 188 else 189 record(errorRenderer, ex.getConstraint()); 190 } 191 192 /** 193 * Invokes {@link #record(IRender, ValidationConstraint)}, after wrapping the message parameter 194 * in a {@link RenderString}. 195 */ 196 197 public void record(String message, ValidationConstraint constraint) 198 { 199 record(new RenderString(message), constraint); 200 } 201 202 /** 203 * Records error information about the currently selected component, or records unassociated 204 * (with any field) errors. 205 * <p> 206 * Currently, you may have at most one error per <em>field</em> (note the difference between 207 * field and component), but any number of unassociated errors. 208 * <p> 209 * Subclasses may override the default error message (based on other factors, such as the field 210 * and constraint) before invoking this implementation. 211 * 212 * @since 1.0.9 213 */ 214 215 public void record(IRender errorRenderer, ValidationConstraint constraint) 216 { 217 FieldTracking tracking = findCurrentTracking(); 218 219 // Note that recording two errors for the same field is not advised; the 220 // second will override the first. 221 222 tracking.setErrorRenderer(errorRenderer); 223 tracking.setConstraint(constraint); 224 } 225 226 /** @since 4.0 */ 227 228 public void record(IFormComponent field, String message) 229 { 230 setFormComponent(field); 231 232 record(message, null); 233 } 234 235 public void recordFieldInputValue(String input) 236 { 237 FieldTracking tracking = findCurrentTracking(); 238 239 tracking.setInput(input); 240 } 241 242 /** 243 * Finds or creates the field tracking for the {@link #setFormComponent(IFormComponent)} 244 * current component. If no current component, an unassociated error is created and 245 * returned. 246 * 247 * @since 3.0 248 */ 249 250 protected FieldTracking findCurrentTracking() 251 { 252 FieldTracking result = null; 253 254 if (_currentComponent == null) 255 { 256 result = new FieldTracking(); 257 258 // Add it to the field trackings, but not to the 259 // map. 260 261 _trackings.add(result); 262 } 263 else 264 { 265 result = getComponentTracking(); 266 267 if (result == null) 268 { 269 String fieldName = _currentComponent.getName(); 270 271 result = new FieldTracking(fieldName, _currentComponent); 272 273 _trackings.add(result); 274 _trackingMap.put(fieldName, result); 275 } 276 } 277 278 return result; 279 } 280 281 /** 282 * Does nothing. Override in a subclass to decoreate fields. 283 */ 284 285 public void writePrefix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component, 286 IValidator validator) 287 { 288 } 289 290 /** 291 * Does nothing. Override in a subclass to decorate fields. 292 */ 293 294 public void writeAttributes(IMarkupWriter writer, IRequestCycle cycle, 295 IFormComponent component, IValidator validator) 296 { 297 } 298 299 /** 300 * Default implementation; if the current field is in error, then a suffix is written. The 301 * suffix is: <code>&nbsp;<font color="red">**</font></code>. 302 */ 303 304 public void writeSuffix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component, 305 IValidator validator) 306 { 307 if (isInError()) 308 { 309 writer.printRaw(" "); 310 writer.begin("font"); 311 writer.attribute("color", "red"); 312 writer.print("**"); 313 writer.end(); 314 } 315 } 316 317 public boolean getHasErrors() 318 { 319 return getFirstError() != null; 320 } 321 322 /** 323 * A convienience, as most pages just show the first error on the page. 324 * <p> 325 * As of release 1.0.9, this returns an instance of {@link IRender}, not a {@link String}. 326 */ 327 328 public IRender getFirstError() 329 { 330 if (Tapestry.size(_trackings) == 0) 331 return null; 332 333 Iterator i = _trackings.iterator(); 334 335 while (i.hasNext()) 336 { 337 IFieldTracking tracking = (IFieldTracking) i.next(); 338 339 if (tracking.isInError()) 340 return tracking.getErrorRenderer(); 341 } 342 343 return null; 344 } 345 346 /** 347 * Checks to see if the field is in error. This will <em>not</em> work properly in a loop, but 348 * is only used by {@link FieldLabel}. Therefore, using {@link FieldLabel}in a loop (where the 349 * {@link IFormComponent}is renderred more than once) will not provide correct results. 350 */ 351 352 protected boolean isInError(IFormComponent component) 353 { 354 // Get the name as most recently rendered. 355 356 String fieldName = component.getName(); 357 358 IFieldTracking tracking = (IFieldTracking) _trackingMap.get(fieldName); 359 360 return tracking != null && tracking.isInError(); 361 } 362 363 /** 364 * Returns a {@link List}of {@link IFieldTracking}s. This is the master list of trackings, 365 * except that it omits and trackings that are not associated with a particular field. May 366 * return an empty list, or null. 367 * <p> 368 * Order is not determined, though it is likely the order in which components are laid out on in 369 * the template (this is subject to change). 370 */ 371 372 public List getAssociatedTrackings() 373 { 374 int count = Tapestry.size(_trackings); 375 376 if (count == 0) 377 return null; 378 379 List result = new ArrayList(count); 380 381 for (int i = 0; i < count; i++) 382 { 383 IFieldTracking tracking = (IFieldTracking) _trackings.get(i); 384 385 if (tracking.getFieldName() == null) 386 continue; 387 388 result.add(tracking); 389 } 390 391 return result; 392 } 393 394 /** 395 * Like {@link #getAssociatedTrackings()}, but returns only the unassociated trackings. 396 * Unassociated trackings are new (in release 1.0.9), and are why interface 397 * {@link IFieldTracking}is not very well named. 398 * <p> 399 * The trackings are returned in an unspecified order, which (for the moment, anyway) is the 400 * order in which they were added (this could change in the future, or become more concrete). 401 */ 402 403 public List getUnassociatedTrackings() 404 { 405 int count = Tapestry.size(_trackings); 406 407 if (count == 0) 408 return null; 409 410 List result = new ArrayList(count); 411 412 for (int i = 0; i < count; i++) 413 { 414 IFieldTracking tracking = (IFieldTracking) _trackings.get(i); 415 416 if (tracking.getFieldName() != null) 417 continue; 418 419 result.add(tracking); 420 } 421 422 return result; 423 } 424 425 public List getErrorRenderers() 426 { 427 List result = new ArrayList(); 428 429 Iterator i = _trackings.iterator(); 430 while (i.hasNext()) 431 { 432 IFieldTracking tracking = (IFieldTracking) i.next(); 433 434 IRender errorRenderer = tracking.getErrorRenderer(); 435 436 if (errorRenderer != null) 437 result.add(errorRenderer); 438 } 439 440 return result; 441 } 442 443 /** @since 4.0 */ 444 445 public void registerForFocus(IFormComponent field, int priority) 446 { 447 if (priority > _focusPriority) 448 { 449 _focusField = field.getClientId(); 450 _focusPriority = priority; 451 } 452 } 453 454 /** 455 * Returns the focus field, or null if no form components registered for focus (i.e., they were 456 * all disabled). 457 */ 458 459 public String getFocusField() 460 { 461 return _focusField; 462 } 463 464 }