001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.web.forms; 007 008import jakarta.servlet.*; 009import jakarta.servlet.http.*; 010import java.io.*; 011import java.util.*; 012import java.sql.*; 013 014import fc.jdbc.*; 015import fc.io.*; 016import fc.util.*; 017 018/** 019Represents an HTML form field. Subclasses provide concrete representations 020of various HTML input types. 021<p> 022Note: A {@link FieldValidator} for a given field is added to that field 023automatically by a new Validator when that validator is instantiated (so a 024public <code>addValidator</code> method is not needed in this class). 025<p> 026Fields are normally created and added to a form once. However, new fields can 027also be created per user/per-submit and added to the {@link FormData} object. 028These fields can then be rendered via the {@link Form#renderDynamicFields} 029method and are shown <i>just</i> for that request. They are not part of the form and 030cannot be later retrieved by {@link Form#get} -- use {Form#getSubmittedField} 031instead. 032<p> 033Initial values (if any) for fields are typically specified during form 034instantiation. These default values are initially shown to all users when 035the form is rendered. However, in some cases, it is possible to show 036different initial values to different users. This is done by creating a new 037{@link FormData} object before the form is rendered and calling the 038<code>setValue(FormData, ...)</code> methods on various fields to set values 039on the formdata object. 040<p> 041FormData objects are automatically created to store submitted form data 042(and maintain form state) by the {@link Form#handleSubmit} method. By manually 043creating a new FormData object before showing the form, and the using that 044object to render the form, per-user initial values can be displayed (this 045simulates a user form submission which of course is different for each 046user). 047<p> 048This form api is complex. For a simpler bare-bones alternative, see 049{@link fc.web.simpleforms} 050 051@author hursh jain 052**/ 053public abstract class Field 054{ 055static protected SystemLog log; 056 057protected Form form; 058protected String name; 059protected List arbitraryString; 060protected boolean renderStyleTag; 061protected String styleTag; 062protected String label; 063protected List validators; 064protected FieldRefresher refresher; 065protected boolean enabled = true; 066 067/** 068Creates a new form Field. By default, a field is enabled. 069To disable a field (so that it will not be validated), 070use the {@link #enable(FormData)} method. 071 072@param fieldName the name of this field 073**/ 074public Field(String fieldName) 075 { 076 Argcheck.notnull(fieldName, "fieldName parameter was null"); 077 this.name = fieldName; 078 log = Log.getDefault(); 079 renderStyleTag = true; 080 styleTag = this.name; 081 validators = new ArrayList(); 082 arbitraryString = new ArrayList(8); 083 } 084 085/** 086Not used any more, replaced by getTypeXXX methods 087 088Returns the values that were last selected or submitted. The returned 089object's runtime type will depend upon the subclass and could be, for 090example, a String, String[] or a List. (the subclass should document the 091returned type) abstract public Object getValue(); 092**/ 093 094/** Returns the name of this form element **/ 095public String getName() { 096 return name; 097 } 098 099/** 100Subclasses should return an appropriate {@link Field.Type}. This type 101is rendered as part of <code><input type= ...</code> 102**/ 103abstract public Field.Type getType(); 104 105/** 106This method sets the value of this field from the parameters obtained from 107the specified request. The name of the parameter to obtain this value will 108typically be the name of this field itself. 109 110@param fd the form data object to store the value in 111**/ 112abstract public void setValueFromSubmit(FormData fd, HttpServletRequest req) 113throws SubmitHackedException; 114 115/** 116Returns <code>true</code> if this field was isFilled out or selected by the 117user, <code>false</code> otherwise. 118<p> 119Note: Some fields like selects will never be empty since non-multiple 120select fields always send their default selected value. [although 121select/with/multiple can be empty since the browser sends (much like radio 122buttoms) nothing at all when no option is selected]. 123*/ 124abstract public boolean isFilled(FormData fd); 125 126 127public abstract void renderImpl(FormData fd, Writer writer) 128throws SQLException, IOException; 129 130/** 131This method writes the HTML for this form field to the given writer. Only 132the HTML for the field itself should be written and the enclosing HTML for 133this field is left upto the calling code. (typically a dynamic server side 134page). The field should be rendered in a sticky fashion, i.e., should 135incorporate/display the last submitted value. 136<p> 137<b>Important Note:</b> Although each field renders itself, validation 138errors are not rendered as part of this rendering. This is specifically 139designed for flexibility in HTML layout and display of error messages. This 140does imply that page authors <span style="color:blue"><b>must</b></span> remember 141to extract and write error messages from each field. 142<p> 143<b>Important Note 2:</b> If providing a non-null argument for the FormData 144parameter, this method should only be called after calling {@link 145Form#handleSubmit}. FormData objects hold the results of submitted form 146data. <b>If the formdata is <code>null</code>, it implies that the form is 147shown to the user for the first time. The fields then render themselves 148based on their default/initial values.</b>. However, one can also display 149different initial values unique to a user (say by retrieving their last 150filled values from a database). In that case, a FormData object should be 151initially/manually created and passed to this method. 152 153@param fd the current form data object (can be null). 154@param writer the output destination 155**/ 156public void render(final FormData fd, final Writer writer) throws SQLException, IOException 157 { 158 if (refresher != null) 159 { 160 /* always refreshers are request specific and hence need a valid fd */ 161 if (fd == null) 162 log.info("Looks like Field:[", name, "] is being rendered for the first time. Attached ", refresher, " will not be invoked. Remember to construct/init the field with an appropriate value to see initial values."); 163 else 164 refresher.refresh(fd); 165 } 166 167 renderImpl(fd, writer); 168 } 169 170/** 171Convenience method that calls <code>render(null, writer)</code> 172(for when the form needs to be displayed the first time 173without any formdata) 174*/ 175public void render(Writer writer) throws SQLException, IOException { 176 render(null, writer); 177 } 178 179/** 180Similar to {@link #render(FormData,Writer)} but also renders the specified 181prefix/suffix strings before and after each element. For example, one can 182say (in a jsp): 183<blockquote> 184<code><td><% form.get("foo").render(); %><br></td></code> 185</blockquote> 186<i>or</i>: 187<blockquote> 188<pre><% form.get("foo").render("<td>", "<br></td>"); %></pre> 189</blockquote> 190 191@param fd the current form data object (if any). Form data objects 192 hold the results of submitted form data. This might be 193 <code>null</code> initially (when the form is show to the user 194 for the first time). 195@param writer the output destination 196@param prefix prefix value for each element. specify 197 <code>null</code> or an empty string if not needed. 198@param suffix suffix value for each element. specify 199 <code>null</code> or an empty string if not needed. 200*/ 201public void render(FormData fd, Writer writer, String prefix, String suffix) 202throws SQLException, IOException 203 { 204 if (prefix != null) 205 writer.write(prefix); 206 render(fd, writer); 207 if (suffix != null) 208 writer.write(suffix); 209 } 210 211/** 212Calls {@link #renderError(FormData, Writer)} with <br> as 213the message seperator string. 214**/ 215public Field renderError(FormData fd, Writer writer) throws IOException 216 { 217 return renderError(fd, writer, "<br>\n"); 218 } 219 220/** 221Convenience method to render validation errors. Normally, a jsp would 222always call {@link #getValidateErrors} and: 223<ul> 224 <li>if the returned data was null would write an empty 225 string or nothing 226 <li>the list of error messages adjacent to the html field 227 itself. 228</ul> 229<p> 230This method does exactly that: if applicable it writes validation error 231messages, each seperated by the specfied message seperator parameter. 232<p> 233If this field is rendering style tags, then the error messages are 234encapulated in a html <span> tag and the class of the span tag is set 235to <code>form-errmsg</code> 236 237@param writer the Writer to render errors to 238@param htmlSep the seperator string (any string or html tag/fragment) 239 to separate messages (if there are more than 1 240 error messages) 241 242@return this field object for method chaining convenience 243**/ 244public Field renderError(FormData fd, Writer writer, String htmlSep) throws IOException 245 { 246 List errors = getValidateErrors(fd); 247 248 if (errors == null) { 249 return this; 250 } 251 252 if (renderStyleTag) { 253 writer.write("<span class='form-errmsg'>"); 254 } 255 256 Iterator it = errors.iterator(); 257 while (it.hasNext()) { 258 String item = (String) it.next(); 259 writer.write(item); 260 writer.write(htmlSep); 261 } 262 if (renderStyleTag) { 263 writer.write("</span>"); 264 } 265 return this; 266 } 267 268/** 269Adds a field level validation error message. This method is needed when 270some validation for this field is done via classes that are <b>not</b> 271subclasses of {@link FieldValidator} (these classes are used outside of the 272validation framework and their error message still needs to be added to the 273field validation error data) 274<p> 275<b> This is a very useful method for adding the results of arbitrary code 276to this form. 277</b> 278 279@see Form#addError 280**/ 281public void addError(FormData fd, String errorMessage) 282 { 283 List list = fd.createFieldErrorList(this); 284 list.add(errorMessage); 285 } 286 287/** 288Adds a refresher to this field. The refresher will be invoked to set 289new values for this field before the field is rendered. 290*/ 291public void add(FieldRefresher refresher) 292 { 293 this.refresher = refresher; 294 refresher.field = this; 295 } 296 297/** 298Adds any arbitrary string to the field, which is written as is when 299the field is rendered. (style tags should be set via the {@link #setStyleTag} 300however). 301<p> 302This method can be called as many times as needed. For example: 303<blockquote> 304<pre> 305addString("tabindex=2"); 306addString("onclick='foo();return true;'"); 307</pre> 308</blockquote> 309or to set a arbitrary Javascript string: 310<blockquote> 311<pre> 312addString("onMouseOver='bar()' onClick='foo()'"); 313</pre> 314</blockquote> 315*/ 316public void addString(String str) 317 { 318 this.arbitraryString.add(str); 319 } 320 321/** 322Adds some arbitrary text to this field which can later be retrieved via the 323getLabel method and shown as an html label for this field. This is useful 324when programmatically creating fields from a database where the label 325itself is retrieved from the database. 326*/ 327public void addLabel(String label) { 328 this.label = label; 329 } 330 331/** 332Returns the previously set label or <code>null</code> if no 333label was set. 334*/ 335public String getLabel() { 336 return label; 337 } 338 339/** 340Validates this field via the installed validators. This method should be 341called after setting the submitted value of this field via the {@link 342#setValueFromSubmit} method. 343<p> 344No validators are run if the field is not enabled. 345<p> 346Validation errors that result after calling this method (if any) 347can be retrieved via the {@link #getValidateErrors} method. 348 349@return <code>false</code> on a validation error, <code>true</code> otherwise. 350**/ 351public boolean validate(FormData fd, HttpServletRequest req) 352 { 353 //fast return; 354 int size = validators.size(); 355 if ( size == 0 ) 356 return true; 357 358 boolean result = true; 359 List errors = null; 360 361 for (int n = 0; n < size; n++) 362 { 363 FieldValidator v = (FieldValidator) validators.get(n); 364 365 if (! v.validate(fd, req)) { 366 result = false; //latch 367 String tmp = v.getErrorMessage(); 368 log.bug("Field validate error: ", name, tmp); 369 errors = fd.createFieldErrorList(this); 370 errors.add(tmp); 371 } 372 } 373 374 return result; 375 } 376 377/** 378Returns a List of Strings containing validation errors. Items in this list 379are a collection of all error messages (Strings) returned by validators 380that validate this field. 381 382If there are no validation errors, the returned list will be null. 383**/ 384public List getValidateErrors(FormData fd) 385 { 386 if (fd == null) 387 return null; 388 389 List list = fd.getFieldErrors(this); //can be null 390 391 return list; 392 } 393 394/** 395Adds a validator for this field. During validation, validators are 396sequentially called in the order they were added by this method. Note, this 397method is package-private since it need only be called from a {@link 398FieldValidator} constructor. 399**/ 400void addValidator(FieldValidator validator) 401 { 402 validators.add(validator); 403 } 404 405List getValidators() 406 { 407 return validators; 408 } 409 410/* 411Returns <tt>true</tt> is this form element is required, <tt>false</tt> 412otherwise. Required means that the value of this field cannot be null. 413*/ 414/* No need -- use a validator 415public boolean isRequired() 416 { 417 return required; 418 } 419*/ 420 421/* 422Specify <tt>true</tt> if this form element is required, 423<tt>false</tt> otherwise. By default, fields are required. 424*/ 425/*No need -- use a validator 426public void setRequired(boolean val) 427 { 428 required = val; 429 } 430*/ 431 432/** 433Enables this field for this particular request. Enabled fields run 434through all attached validators. Non enabled fields skip all validation. 435<p> 436Note, since we use the {@link FormData} to keep track of enable/disable 437status, fields can be enabled/disabled per user (independent of other 438users). 439**/ 440public void enable(FormData fd) 441 { 442 if (fd != null) 443 fd.enableField(name, true); 444 } 445 446/** 447Disabled this field for this particular request. Disabled fields skip all 448validation. 449<p> 450Note, since we use the {@link FormData} to keep track of enable/disable 451status, fields can be enabled/disabled per user (indepenent of other 452users). 453**/ 454public void disable(FormData fd) 455 { 456 if (fd != null) 457 fd.enableField(name, false); 458 } 459 460/** 461Enables this field for all users. 462*/ 463public void enable() 464 { 465 enabled = true; 466 } 467 468/** 469Disables this field for all users. 470*/ 471public void disable() 472 { 473 enabled = false; 474 } 475 476public boolean isEnabled(FormData fd) 477 { 478 if (fd == null) 479 return true; 480 481 return fd.isEnabled(name); 482 } 483 484/** 485Sets the parent form for this field. This method is automatically called by 486a form object when this field is added to that form. This saves us from 487having to pass in a form reference in the constructor for this field object 488(which can be a hassle when constructing many field objects). Visibility is 489package-private since this method need only be called from the base form 490class. 491**/ 492void setParentForm(Form form) 493 { 494 Argcheck.notnull(form, "form parameter was null"); 495 this.form = form; 496 } 497 498/** 499Sets a style tag to be rendered (overrides the default class tag of 500<code>formName-inputName</code>. 501*/ 502public void setStyleTag(String value) 503 { 504 styleTag = value; 505 } 506 507/** 508Set to <code>true</code> if the element should render a style tag, <code>false</code> 509otherwise. By default this value is <code>true</code>. 510<p> 511An arbitrary style class can be set for a field by calling the {@link 512#setStyleTag} method. If a custom style class is <b>not</b> set via this 513method, style tags are rendered as: 514 515<blockquote><pre> 516class=formName-inputName 517</pre></blockquote> 518 519So, for example, a input type of name <code>myname</code> contained in 520the form <code>myform</code> will render it's style tag as the following 521class: 522 523<blockquote><pre> 524<input ..... class="myform-myname" ....> 525</pre></blockquote> 526Note, <code>.</code> and <code>_</code> are not allowed in css class names 527(for legacy reasons) but <code>-</code> <i>is</i> allowed. 528**/ 529public void renderStyleTag(boolean val) 530 { 531 renderStyleTag = val; 532 } 533 534/** 535Logs an unexpected submission state, typical of a hacked input form 536*/ 537protected void hacklert(HttpServletRequest req, String msg) 538throws SubmitHackedException 539 { 540 form.handleSubmitHacked(req, msg); 541 } 542 543public String toString() 544 { 545 StringBuffer buf = new StringBuffer(256); 546 buf.append("Field name='").append(name); 547 548 /* 549 can be null: if field was added to the form then the form sets itself 550 as the parent of that field. However, if the field was a hidden field 551 created dynamically and added to the formdata object, then there is no 552 parent form. this can then be null. 553 */ 554 if (form != null) { 555 buf.append("' (@form=").append(form.getName()).append(")"); 556 } 557 558 buf.append("; type='").append(getType()).append("']"); 559 if (refresher != null) { 560 buf.append(" Attached refresher: "); 561 buf.append(refresher); 562 } 563 return buf.toString(); 564 } 565 566/** 567Encapsulates different types of HTML form elements. Needed 568because various subclasses return a specific type which is 569then used internally inside the {@link #render} method ( 570each HTML form element must have a type for example, 571<code><input <b>type=radio</b>...></code> or <code><input 572<b>type=text</b>....></code> etc.) 573**/ 574public static class Type 575 { 576 private String name; 577 private Type(String name) { 578 this.name = name; 579 } 580 public String getName() { 581 return name; 582 } 583 584 public static final Type CHECKBOX = new Type("checkbox"); 585 public static final Type HIDDEN = new Type("hidden"); 586 public static final Type TEXT = new Type("text"); 587 public static final Type TEXTAREA = new Type("textarea"); 588 public static final Type PASSWORD = new Type("password"); 589 //no separate radiogroup since no such thing in HTML 590 //radiogroups = radio's with same name 591 public static final Type RADIO = new Type("radio"); 592 public static final Type SELECT = new Type("select"); 593 594 public String toString() { 595 return name; 596 } 597 } //~ class Type 598 599} //~class Field