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