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>&lt;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>&lt;td&gt;&lt;% form.get("foo").render(); %&gt;&lt;br&gt;&lt;/td&gt;</code>
185</blockquote>
186<i>or</i>:
187<blockquote>
188<pre>&lt;% form.get("foo").render("&lt;td&gt;", "&lt;br&gt;&lt;/td&gt;"); %&gt;</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 &lt;br&gt; 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 &lt;span&gt; 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&lt;input ..... class="myform-myname" ....&gt;
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>&lt;input <b>type=radio</b>...&gt;</code> or <code>&lt;input
572<b>type=text</b>....&gt;</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