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>&lt;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 &lt;br&gt; 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 &lt;span&gt; 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>&lt;input <b>type=radio</b>...&gt;</tt> or <tt>&lt;input
574    <b>type=text</b>....&gt;</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