org.apache.click.control.Form.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.click.control.Form.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.click.control;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.click.Context;
import org.apache.click.Control;
import org.apache.click.Page;
import org.apache.click.Stateful;
import org.apache.click.element.CssImport;
import org.apache.click.element.Element;
import org.apache.click.element.JsImport;
import org.apache.click.service.FileUploadService;
import org.apache.click.service.LogService;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.ContainerUtils;
import org.apache.click.util.HtmlStringBuffer;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.lang.StringUtils;

/**
 * Provides a Form control:   <form method='post'>.
 *
 * <table class='htmlHeader' cellspacing='12'>
 * <tr>
 * <td>
 *
 * <table class='fields'>
 * <tr>
 * <td align='left'><label>Username</label><span class="red">*</span></td>
 * <td align='left'><input type='text' name='username' value='' size='20' maxlength='20' /></td>
 * </tr>
 * <tr>
 * <td align='left'><label>Password</label><span class="red">*</span></td>
 * <td align='left'><input type='password' name='password' value='' size='20' maxlength='20' /></td>
 * </tr>
 * </table>
 * <table class="buttons">
 * <tr><td>
 * <input type='submit' name='ok' value='  OK  '/>&nbsp;<input type='submit' name='cancel' value=' Cancel '/>
 * </td></tr>
 * </table>
 *
 * </td>
 * </tr>
 * </table>
 *
 * When a Form is processed it will process its {@link Field} controls
 * in the order they were added to the form, and then it will process the
 * {@link Button} controls in the added order. Once all the Fields have been
 * processed the form will invoke its action listener if defined.
 *
 * <h3>Form Example</h3>
 *
 * The example below illustrates a Form being used in a login Page.
 *
 * <pre class="prettyprint">
 * public class Login extends Page {
 *
 *     public Form form = new Form();
 *
 *     public Login() {
 *         form.add(new TextField("username", true));
 *         form.add(new PasswordField("password", true));
 *         form.add(new Submit("ok", "  OK  ", this, "onOkClick"));
 *         form.add(new Submit("cancel", this, "onCancelClick"));
 *     }
 *
 *     public boolean onOkClick() {
 *         if (form.isValid()) {
 *             User user = new User();
 *             form.copyTo(user);
 *
 *             if (getUserService().isAuthenticatedUser(user)) {
 *                 getContext().setSessionAttribute("user", user);
 *                 setRedirect(HomePage.class);
 *             } else {
 *                 form.setError(getMessage("authentication-error"));
 *             }
 *         }
 *         return true;
 *     }
 *
 *     public boolean onCancelClick() {
 *         setRedirect(WelcomePage.class);
 *         return false;
 *     }
 * } </pre>
 *
 * The forms corresponding template code is below. Note the form automatically
 * renders itself when Velocity invokes its {@link #toString()} method.
 *
 * <pre class="codeHtml">
 * <span class="blue">$form</span> </pre>
 *
 * If a Form has been posted and processed, if it has an {@link #error} defined or
 * any of its Fields have validation errors they will be automatically
 * rendered, and the {@link #isValid()} method will return false.
 *
 * <a name="data-binding"></a>
 * <h3>Data Binding</h3>
 *
 * To bind value objects to a forms fields use the copy methods:
 * <ul>
 * <li>value object &nbsp; -> &nbsp; form fields  &nbsp; &nbsp; &nbsp;
 * {@link #copyFrom(Object)}</li>
 * <li>form fields &nbsp; -> &nbsp; value object  &nbsp; &nbsp; &nbsp;
 * {@link #copyTo(Object)}</li>
 * </ul>
 * To debug the data binding being performed, use the Click application mode to
 * "<tt>debug</tt>" or use the debug copy methods.
 * <p/>
 * Binding of nested data objects is supported using the
 * <a target="blank" href="http://www.ognl.org">OGNL</a> library. To use
 * nested objects in your form, simply specify the object path as the Field
 * name. Note in the object path you exclude the root object, so the path
 * <tt>customer.address.state</tt> is specified as <tt>address.state</tt>.
 * <p/>
 * For example:
 *
 * <pre class="prettyprint">
 * // The customer.address.state field
 * TextField stateField = new TextField("address.state");
 * form.add(stateField);
 * ..
 *
 * // Loads the customer address state into the form stateField
 * Customer customer = getCustomer();
 * form.copyFrom(customer);
 * ..
 *
 * // Copies form stateField value into the customer address state
 * Customer customer = new Customer();
 * form.copyTo(customer); </pre>
 *
 * When populating an object from a form post Click will automatically create
 * any null nested objects so their properties can be set. To do this Click
 * uses the no-args constructor of the nested objects class.
 * <p/>
 * {@link #copyTo(Object)} and {@link #copyFrom(Object)} also supports
 * <tt>java.util.Map</tt> as an argument. Examples of using
 * <tt>java.util.Map</tt> are shown in the respective method descriptions.
 *
 * <a name="form-validation"></a>
 * <h3>Form Validation</h3>
 *
 * The Form control supports automatic field validation. By default when a POST
 * request is made the form will validate the field values. To disable
 * automatic validation set {@link #setValidate(boolean)} to false.
 * <p/>
 * Form also provides a {@link #validate()} method where subclasses can provide
 * custom cross-field validation.
 * <p/>
 * <b>File Upload Validation</b>
 * <p/>
 * The Form's {@link #validateFileUpload()} provides validation for multipart
 * requests (multipart requests are used for uploading files from the browser).
 * The {@link #validateFileUpload()} method checks that files being uploaded do not exceed the
 * {@link org.apache.click.service.CommonsFileUploadService#sizeMax maximum request size}
 * or the {@link org.apache.click.service.CommonsFileUploadService#fileSizeMax maximum file size}.
 * <p/>
 * <b>Note:</b> if the <tt>maximum request size</tt> or <tt>maximum file size</tt>
 * is exceeded, the request is deemed invalid ({@link #hasPostError hasPostError}
 * will return true), and no further processing is performed on the form or fields.
 * Instead the form will display the appropriate error message for the invalid request.
 * See {@link #validateFileUpload()} for details of the error message properties.
 * <p/>
 * <b>JavaScript Validation</b>
 * <p/>
 * The Form control also supports client side JavaScript validation. By default
 * JavaScript validation is not enabled. To enable JavaScript validation set
 * {@link #setJavaScriptValidation(boolean)} to true. For example:
 *
 * <pre class="prettyprint">
 * Form form = new Form("form");
 * form.setJavaScriptValidation(true);
 *
 * // Add form fields
 * ..
 *
 * form.add(new Submit("ok", " OK ", this, "onOkClicked");
 *
 * Submit cancel = new Submit("cancel", "Cancel", this, "onCancelClicked");
 * cancel.setCancelJavaScriptValidation(true);
 *
 * addControl(form); </pre>
 *
 * Please note in that is this example the cancel submit button has
 * {@link Submit#setCancelJavaScriptValidation(boolean)} set to true. This
 * prevents JavaScript form validation being performed when the cancel button is
 * clicked.
 *
 * <a name="resources"></a>
 * <h3>CSS and JavaScript resources</h3>
 *
 * The Form control makes use of the following resources (which Click automatically
 * deploys to the application directory, <tt>/click</tt>):
 *
 * <ul>
 * <li><tt>click/control.css</tt></li>
 * <li><tt>click/control.js</tt></li>
 * </ul>
 *
 * To import these files and any form control imports simply reference
 * the variables <span class="blue">$headElements</span> and
 * <span class="blue">$jsElements</span> in the page template. For example:
 *
 * <pre class="codeHtml">
 * &lt;html&gt;
 * &lt;head&gt;
 * <span class="blue">$headElements</span>
 * &lt;/head&gt;
 * &lt;body&gt;
 *
 * <span class="red">$form</span>
 *
 * <span class="blue">$jsElements</span>
 * &lt;/body&gt;
 * &lt;/html&gt; </pre>
 *
 * <a name="form-layout"></a>
 * <h3>Form Layout</h3>
 * The Form control supports rendering using automatic and manual layout
 * techniques.
 *
 * <a name="auto-layout"></a>
 * <h4>Auto Layout</h4>
 *
 * If you include a form variable in your template the form will be
 * automatically laid out and rendered. Auto layout, form and field rendering
 * options include:
 *
 * <table style="margin-left: 1em;" cellpadding="3">
 * <tr>
 * <td>{@link #buttonAlign}</td> <td>button alignment: &nbsp; <tt>["left", "center", "right"]</tt></td>
 * </tr><tr>
 * <td>{@link #buttonStyle}</td> <td>button &lt;td&gt; "style" attribute value</td>
 * </tr><tr>
 * <td>{@link #columns}</td> <td>number of form table columns, the default value number is 1</td>
 * </tr><tr>
 * <td>{@link #errorsAlign}</td> <td>validation error messages alignment: &nbsp; <tt>["left", "center", "right"]</tt></td>
 * </tr><tr>
 * <td>{@link #errorsPosition}</td> <td>validation error messages position: &nbsp; <tt>["top", "middle", "bottom"]</tt></td>
 * </tr><tr>
 * <td>{@link #errorsStyle}</td> <td>errors &lt;td&gt; "style" attribute value</td>
 * </tr><tr>
 * <td>{@link #fieldStyle}</td> <td>field &lt;td&gt; "style" attribute value</td>
 * </tr><tr>
 * <td>{@link #labelAlign}</td> <td>field label alignment: &nbsp; <tt>["left", "center", "right"]</tt></td>
 * </tr><tr>
 * <td>{@link #labelsPosition}</td> <td>label position relative to field: &nbsp; <tt>["left", "top"]</tt></td>
 * </tr><tr>
 * <td>{@link #labelStyle}</td> <td>label &lt;td&gt; "style" attribute value</td>
 * </tr><tr>
 * <td>click/control.css</td> <td>control CSS styles, automatically deployed to the <tt>click</tt> web directory</td>
 * </tr><tr>
 * <td>/click-control.properties</td> <td>form and field messages and HTML, located under classpath</td>
 * </tr>
 * </table>
 *
 * <a name="manual-layout"></a>
 * <h4>Manual Layout</h4>
 *
 * You can also manually layout the Form in the page template specifying
 * the fields using the named field notation:
 *
 * <pre class="codeHtml">
 * $form.{@link #getFields fields}.usernameField </pre>
 *
 * Whenever including your own Form markup in a page template or Velocity macro
 * always specify:
 * <ul style="margin-top: 0.5em;">
 *  <li><span class="maroon">method</span>
 *      - the form submission method <tt>["post" | "get"]</tt></li>
 *  <li><span class="maroon">name</span>
 *      - the name of your form, important when using JavaScript</li>
 *  <li><span class="maroon">action</span>
 *      - directs the Page where the form should be submitted to</li>
 *  <li><span class="maroon">form_name</span>
 *      - include a hidden field which specifies the {@link #name} of the Form </li>
 * </ul>
 * The hidden field is used by Click to determine which form was posted on
 * a page which may contain multiple forms.
 * <p/>
 * Alternatively you can use the Form {@link #startTag()} and {@link #endTag()}
 * methods to render this information.
 * <p/>
 * An example of a manually laid out Login form is provided below:
 *
 * <pre class="codeHtml">
 * <span class="blue">$form.startTag()</span>
 *
 *   &lt;table style="margin: 1em;"&gt;
 *
 *     <span class="red">#if</span> (<span class="blue">$form.error</span>)
 *     &lt;tr&gt;
 *       &lt;td colspan="2" style="color: red;"&gt; <span class="blue">$form.error</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *     <span class="red">#end</span>
 *     <span class="red">#if</span> (<span class="blue">$form.fields.usernameField.error</span>)
 *     &lt;tr&gt;
 *       &lt;td colspan="2" style="color: red;"&gt; <span class="blue">$form.fields.usernameField.error</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *     <span class="red">#end</span>
 *     <span class="red">#if</span> (<span class="blue">$form.fields.passwordField.error</span>)
 *     &lt;tr&gt;
 *       &lt;td colspan="2" style="color: red;"&gt; <span class="blue">$form.fields.passwordField.error</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *     <span class="red">#end</span>
 *
 *     &lt;tr&gt;
 *       &lt;td&gt; Username: &lt;/td&gt;
 *       &lt;td&gt; <span class="blue">$form.fields.usernameField</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *     &lt;tr&gt;
 *       &lt;td&gt; Password: &lt;/td&gt;
 *       &lt;td&gt; <span class="blue">$form.fields.passwordField</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *
 *     &lt;tr&gt;
 *       &lt;td&gt;
 *         <span class="blue">$form.fields.okSubmit</span>
 *         <span class="blue">$form.fields.cancelSubmit</span>
 *       &lt;/td&gt;
 *     &lt;/tr&gt;
 *
 *   &lt;/table&gt;
 *
 * <span class="blue">$form.endTag()</span> </pre>
 *
 * As you can see in this example most of the code and markup is generic and
 * could be reused. This is where Velocity Macros come in.
 *
 * <a name="velocity-macros"></a>
 * <h4>Velocity Macros</h4>
 *
 * Velocity Macros
 * (<a target="topic" href="../../../../../velocity/user-guide.html#Velocimacros">velocimacros</a>)
 * are a great way to encapsulate customized forms.
 * <p/>
 * To create a generic form layout you can use the Form {@link #getFieldList()} and
 * {@link #getButtonList()} properties within a Velocity macro. If you want to
 * access <em>all</em> Form Controls from within a Velocity template or macro use
 * {@link #getControls()}.
 * <p/>
 * The example below provides a generic <span class="green">writeForm()</span>
 * macro which you could use through out an application. This Velocity macro code
 * would be contained in a macro file, e.g. <tt>macro.vm</tt>.
 *
 * <pre class="codeHtml"> <span class="red">#*</span> Custom Form Macro Code <span class="red">*#</span>
 * <span class="red">#macro</span>( <span class="green">writeForm</span>[<span class="blue">$form</span>] )
 *
 * <span class="blue">$form.startTag()</span>
 *
 * &lt;table width="100%"&gt;
 *
 * <span class="red">#if</span> (<span class="blue">$form.error</span>)
 *   &lt;tr&gt;
 *     &lt;td colspan="2" style="color: red;"&gt; <span class="blue">$form.error</span> &lt;/td&gt;
 *   &lt;/tr&gt;
 * <span class="red">#end</span>
 *
 * <span class="red">#foreach</span> (<span class="blue">$field</span> <span class="red">in</span> <span class="blue">$form.fieldList</span>)
 *   <span class="red">#if</span> (!<span class="blue">$field.hidden</span>)
 *     <span class="red">#if</span> (!<span class="blue">$field.valid</span>)
 *     &lt;tr&gt;
 *       &lt;td colspan="2"&gt; <span class="blue">$field.error</span> &lt;/td&gt;
 *     &lt;/tr&gt;
 *     <span class="red">#end</span>
 *
 *   &lt;tr&gt;
 *     &lt;td&gt; <span class="blue">$field.label</span>: &lt;/td&gt;&lt;td&gt; <span class="blue">$field</span> &lt;/td&gt;
 *   &lt;/tr&gt;
 *   <span class="red">#end</span>
 * <span class="red">#end</span>
 *
 *  &lt;tr&gt;
 *    &lt;td colspan="2"&gt;
 *    <span class="red">#foreach</span> (<span class="blue">$button</span> <span class="red">in </span><span class="blue">$form.buttonList</span>)
 *      <span class="blue">$button</span> &amp;nbsp;
 *    <span class="red">#end</span>
 *    &lt;/td&gt;
 *  &lt;/tr&gt;
 *
 * &lt;/table&gt;
 *
 * <span class="blue">$form.endTag()</span>
 *
 * <span class="red">#end</span> </pre>
 *
 * You would then call this macro in your Page template passing it your
 * <span class="blue">form</span> object:
 *
 * <pre class="codeHtml"> <span class="red">#</span><span class="green">writeForm</span>(<span class="blue">$form</span>) </pre>
 *
 * At render time Velocity will execute the macro using the given form and render
 * the results to the response output stream.
 *
 * <h4>Configuring Macros</h4>
 *
 * To configure your application to use your macros you can:
 * <ul>
 *  <li>
 *   Put your macros if a file called <span class="st"><tt>macro.vm</tt></span>
 *   in your applications root directory.
 *  </li>
 *  <li>
 *   Put your macros in the auto deployed
 *   <span class="st"><tt>click/VM_global_macro.vm</tt></span> file.
 *  </li>
 *  <li>
 *   Create a custom named macro file and reference it in a
 *   <span class="st"><tt>WEB-INF/velocity.properties</tt></span>
 *   file under the property named
 *   <tt>velocimacro.library</tt>.
 *  </li>
 * </ul>
 *
 * <a name="post-redirect"></a>
 * <h3>Preventing Accidental Form Posts</h3>
 *
 * Users may accidentally make multiple form submissions by refreshing a page
 * or by pressing the back button.
 * <p/>
 * To prevent multiple form posts from page refreshes use the Post
 * Redirect pattern. With this pattern once the user has posted a form you
 * redirect to another page. If the user then presses the refresh button, they
 * will making a GET request on the current page. Please see the
 * <a target="blank" href="http://www.theserverside.com/articles/content/RedirectAfterPost/article.html">Redirect After Post</a>
 * article for more information on this topic.
 * <p/>
 * To prevent multiple form posts from use of the browser back button use one
 * of the Form {@link #onSubmitCheck(org.apache.click.Page, String)} methods. For example:
 *
 * <pre class="prettyprint">
 * public class Purchase extends Page {
 *     ..
 *
 *     public boolean onSecurityCheck() {
 *         return form.onSubmitCheck(this, "/invalid-submit.html");
 *     }
 * } </pre>
 *
 * The form submit check methods store a special token in the users session
 * and in a hidden field in the form to ensure a form post isn't replayed.
 *
 * <a name="dynamic-forms"></a>
 * <h3>Dynamic Forms and <em>not</em> validating a request</h3>
 *
 * A common use case for web applications is to create Form fields dynamically
 * based upon user selection. For example if a checkbox is ticked another Field
 * is added to the Form. A simple way to achieve this is using JavaScript
 * to submit the Form when the Field is changed or clicked.
 * <p/>
 * When submitting a Form using JavaScript, it is often desirable to <em>not</em>
 * validate the fields since the user is still filling out the form.
 * To cater for this use case, Form provides the {@link #setValidate(boolean)}
 * to switch off form and field validation. For example:
 *
 * <pre class="prettyprint">
 * public void onInit() {
 *     checkbox.setAttribute("onclick", "form.submit()");
 *
 *     // Since onInit occurs before the onProcess event,
 *     // we have to explicitly bind the submit button in the onInit event if we
 *     // want to check if it was clicked.
 *     // If the submit button wasn't clicked it means the Form was submitted
 *     // using JavaScript and we don't want to validate yet
 *     ClickUtils.bind(submit);
 *
 *     // If submit was not clicked, don't validate
 *     if(form.isFormSubmission() && !submit.isClicked()) {
 *         form.setValidate(false);
 *     }
 * } </pre>
 *
 * <p>&nbsp;<p/>
 * See also the W3C HTML reference:
 * <a class="external" target="_blank" title="W3C HTML 4.01 Specification"
 *    href="http://www.w3.org/TR/html401/interact/forms.html#h-17.3">FORM</a>
 *
 * @see Field
 * @see Submit
 */
public class Form extends AbstractContainer implements Stateful {

    // Constants --------------------------------------------------------------

    private static final long serialVersionUID = 1L;

    /** The align left, form layout constant: &nbsp; <tt>"left"</tt>. */
    public static final String ALIGN_LEFT = "left";

    /** The align center, form layout constant: &nbsp; <tt>"center"</tt>. */
    public static final String ALIGN_CENTER = "center";

    /** The align right, form layout constant: &nbsp; <tt>"right"</tt>. */
    public static final String ALIGN_RIGHT = "right";

    /**
     * The position top, errors and labels form layout constant: &nbsp;
     * <tt>"top"</tt>.
     */
    public static final String POSITION_TOP = "top";

    /**
     * The position middle, errors in middle form layout constant: &nbsp;
     * <tt>"middle"</tt>.
     */
    public static final String POSITION_MIDDLE = "middle";

    /**
     * The position bottom, errors on bottom form layout constant: &nbsp;
     * <tt>"top"</tt>.
     */
    public static final String POSITION_BOTTOM = "bottom";

    /**
     * The position left, labels of left form layout constant: &nbsp;
     * <tt>"left"</tt>.
     */
    public static final String POSITION_LEFT = "left";

    /**
     * The form name parameter for multiple forms: &nbsp; <tt>"form_name"</tt>.
     */
    public static final String FORM_NAME = "form_name";

    /** The HTTP content type header for multipart forms. */
    public static final String MULTIPART_FORM_DATA = "multipart/form-data";

    /**
     * The submit check reserved request parameter prefix: &nbsp;
     * <tt>SUBMIT_CHECK_</tt>.
     */
    public static final String SUBMIT_CHECK = "SUBMIT_CHECK_";

    /** The Form set field focus JavaScript. */
    protected static final String FOCUS_JAVASCRIPT = "<script type=\"text/javascript\"><!--\n"
            + "var field = document.getElementById('$id');\n"
            + "if (field && field.focus && field.type != 'hidden' && field.disabled != true) { field.focus(); };\n"
            + "//--></script>\n";

    // Instance Variables -----------------------------------------------------

    /** The form action URL. */
    protected String actionURL;

    /** The form disabled value. */
    protected boolean disabled;

    /** The form "enctype" attribute. */
    protected String enctype;

    /** The form level error message. */
    protected String error;

    /** The ordered list of fields, excluding buttons. */
    protected final List<Field> fieldList = new ArrayList<Field>();

    /**
     * The form method <tt>["post, "get"]</tt>, default value: &nbsp;
     * <tt>post</tt>.
     */
    protected String method = "post";

    /** The form is readonly flag. */
    protected boolean readonly;

    /** The form validate fields when processing flag. */
    protected boolean validate = true;

    /** The button align, default value is "<tt>left</tt>". */
    protected String buttonAlign = ALIGN_LEFT;

    /** The ordered list of button values. */
    protected final List<Button> buttonList = new ArrayList<Button>(5);

    /** The button &lt;td&gt; "style" attribute value. */
    protected String buttonStyle;

    /**
     * The number of form layout table columns, default value: <tt>1</tt>.
     * <p/>
     * This property is used to layout the number of table columns the form
     * is rendered with using a flow layout style.
     */
    protected int columns = 1;

    /**
     * The default field size, default value: <tt>0</tt>.
     * <p/>
     * If the form default field size is greater than 0, when fields are added
     * to the form the field's size will be set to the default value.
     */
    protected int defaultFieldSize;

    /** The errors block align, default value is <tt>"left"</tt>. */
    protected String errorsAlign = ALIGN_LEFT;

    /**
     * The form errors position <tt>["top", "middle", "bottom"]</tt> default
     * value: &nbsp; <tt>"top"</tt>.
     */
    protected String errorsPosition = POSITION_TOP;

    /** The error &lt;td&gt; "style" attribute value. */
    protected String errorsStyle;

    /** The field &lt;td&gt; "style" attribute value. */
    protected String fieldStyle;

    /** The map of field width values. */
    protected Map<String, Integer> fieldWidths = new HashMap<String, Integer>();

    /** Flag indicating whether this form was submitted. */
    protected Boolean formSubmission;

    /**
     * The JavaScript client side form fields validation flag. By default
     * JavaScript validation is not enabled.
     */
    protected boolean javaScriptValidation;

    /** The label align, default value is <tt>"left"</tt>. */
    protected String labelAlign = ALIGN_LEFT;

    /**
     * The form labels position <tt>["left", "top"]</tt> default value: &nbsp;
     * <tt>"left"</tt>.
     */
    protected String labelsPosition = POSITION_LEFT;

    /** The label &lt;td&gt; "style" attribute value. */
    protected String labelStyle;

    /**
     * Track the index offset when adding Controls. This ensures HiddenFields
     * added by Form does not interfere with Controls added by users.
     */
    private int insertIndexOffset; // Ensures hiddenFields added by Form are always at the end of the controlList

    // Constructors -----------------------------------------------------------

    /**
     * Create a form with the given name.
     *
     * @param name the name of the form
     * @throws IllegalArgumentException if the form name is null
     */
    public Form(String name) {
        setName(name);
    }

    /**
     * Create a form with no name.
     * <p/>
     * <b>Please note</b> the control's name must be defined before it is valid.
     */
    public Form() {
    }

    // Container Impl ---------------------------------------------------------

    /**
     * Add the control to the form at the specified index, and return the
     * added instance.
     * <p/>
     * <b>Please note</b>: if the form contains a control with the same name as
     * the given control, that control will be
     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
     * by the given control. If a control has no name defined it cannot be replaced.
     * <p/>
     * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
     * where the key is the Control name and value is the Control instance.
     * <p/>
     * All controls are available on the {@link #getControls() controls} list
     * at the index they were inserted. If you are only interested in Fields,
     * note that Buttons are available on the {@link #getButtonList() buttonList}
     * while other fields are available on {@link #getFieldList() fieldList}.
     * <p/>
     * The specified index only applies to {@link #getControls() controls}, not
     * {@link #getButtonList() buttonList} or {@link #getFieldList() fieldList}.
     * <p/>
     * <b>Please note</b> if the specified control already has a parent assigned,
     * it will automatically be removed from that parent and inserted into the
     * form.
     *
     * @see Container#insert(org.apache.click.Control, int)
     *
     * @param control the control to add to the container
     * @param index the index at which the control is to be inserted
     * @return the control that was added to the container
     *
     * @throws IllegalArgumentException if the control is null or if the control
     * and container is the same instance or if the Field name is not defined
     *
     * @throws IndexOutOfBoundsException if index is out of range
     * <tt>(index &lt; 0 || index &gt; getControls().size())</tt>
     */
    @Override
    public Control insert(Control control, int index) {

        // Check if container already contains the control
        String controlName = control.getName();
        if (controlName != null) {
            Control currentControl = getControlMap().get(controlName);

            // If container already contains the control do a replace
            if (currentControl != null && !(control instanceof Label)) {

                // Current control and new control are referencing the same object
                // so we exit early
                if (currentControl == control) {
                    return control;
                }

                // If the two controls are different objects, replace the current
                // control with the given control
                return replace(currentControl, control);
            }
        }

        // Adjust index for hidden fields added by Form. CLK-447
        int realIndex = Math.min(index, getControls().size() - insertIndexOffset);

        ContainerUtils.insert(this, control, realIndex, getControlMap());

        if (control instanceof Field) {
            Field field = (Field) control;

            // Add field to either buttonList or fieldList for fast access
            if (field instanceof Button) {
                getButtonList().add((Button) field);

            } else {
                // Adjust index for hidden fields added by Form
                realIndex = Math.min(index, getFieldList().size() - insertIndexOffset);
                getFieldList().add(realIndex, field);
            }

            field.setForm(this);

            if (getDefaultFieldSize() > 0) {
                if (field instanceof TextField) {
                    ((TextField) field).setSize(getDefaultFieldSize());

                } else if (field instanceof FileField) {
                    ((FileField) field).setSize(getDefaultFieldSize());

                } else if (field instanceof TextArea) {
                    ((TextArea) field).setCols(getDefaultFieldSize());
                }
            }
        }

        return control;
    }

    /**
     * Add a Control to the form and return the added instance.
     * <p/>
     * <b>Please note</b>: if the form contains a control with the same name as
     * the given control, that control will be
     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
     * by the given control. If a control has no name defined it cannot be replaced.
     * <p/>
     * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
     * where the key is the Control name and value is the Control instance.
     * <p/>
     * All controls are available on the {@link #getControls() controls} list
     * in the order they were added. If you are only interested in Fields,
     * note that Buttons are available on the {@link #getButtonList() buttonList}
     * while other fields are available on {@link #getFieldList() fieldList}.
     *
     * @see Container#add(org.apache.click.Control)
     *
     * @param control the control to add to the container and return
     * @return the control that was added to the container
     * @throws IllegalArgumentException if the control is null, the Control name
     * is not defined or the container already contains a control with the same
     * name
     */
    @Override
    public Control add(Control control) {
        return super.add(control);
    }

    /**
     * Add the field to the form, and set the fields form property.
     * <p/>
     * <b>Please note</b>: if the form contains a control with the same name as
     * the given control, that control will be
     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
     * by the given control. If a control has no name defined it cannot be replaced.
     * <p/>
     * Fields can be retrieved from the Map {@link #getFields() fields} where
     * the key is the Field name and value is the Field instance.
     * <p/>
     * Buttons are available on the {@link #getButtonList() buttonList} while
     * other fields are available on {@link #getFieldList() fieldList}.
     *
     * @see #add(org.apache.click.Control)
     *
     * @param field the field to add to the form
     * @return the field added to this form
     * @throws IllegalArgumentException if the field is null, the field name
     * is not defined or the form already contains a control with the same name
     */
    public Field add(Field field) {
        add((Control) field);
        return field;
    }

    /**
     * Add the field to the form and specify the field's width in columns.
     * <p/>
     * <b>Please note</b>: if the form contains a control with the same name as
     * the given control, that control will be
     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
     * by the given control. If a control has no name defined it cannot be replaced.
     * <p/>
     * Fields can be retrieved from the Map {@link #getFields() fields} where
     * the key is the Field name and value is the Field instance.
     * <p/>
     * Fields are available on {@link #getFieldList() fieldList}.
     * <p/>
     * Note Button and HiddenField types are not valid arguments for this method.
     *
     * @see #add(org.apache.click.Control)
     *
     * @param field the field to add to the form
     * @param width the width of the field in table columns
     * @return the field added to this form
     * @throws IllegalArgumentException if the field is null, field name is
     * not defined, field is a Button or HiddenField, the form already contains
     * a field with the same name or the width &lt; 1
     */
    public Field add(Field field, int width) {
        add((Control) field, width);
        return field;
    }

    /**
     * Add the control to the form and specify the control's width in columns.
     * <p/>
     * <b>Please note</b>: if the form contains a control with the same name as
     * the given control, that control will be
     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
     * by the given control. If a control has no name defined it cannot be replaced.
     * <p/>
     * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
     * where the key is the Control name and value is the Control instance.
     * <p/>
     * Controls are available on the {@link #getControls() controls} list.
     * <p/>
     * Note Button and HiddenField types are not valid arguments for this method.
     *
     * @see #add(org.apache.click.Control)
     *
     * @param control the control to add to the form
     * @param width the width of the control in table columns
     * @return the control added to this form
     * @throws IllegalArgumentException if the control is null, control is a
     * Button or HiddenField, the form already contains a control with the same
     * name or the width &lt; 1
     */
    public Control add(Control control, int width) {
        if (control instanceof Button || control instanceof HiddenField) {
            String msg = "Not a valid field type: " + control.getClass().getName();
            throw new IllegalArgumentException(msg);
        }
        if (width < 1) {
            throw new IllegalArgumentException("Invalid field width: " + width);
        }

        add(control);

        if (control.getName() != null) {
            getFieldWidths().put(control.getName(), width);
        }
        return control;
    }

    /**
     * Replace the control in the form at the specified index, and return
     * the newly added control.
     *
     * @see org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control)
     *
     * @param currentControl the control currently contained in the form
     * @param newControl the control to replace the current control contained in
     * the form
     * @return the new control that replaced the current control
     *
     * @deprecated this method was used for stateful pages, which have been deprecated
     *
     * @throws IllegalArgumentException if the currentControl or newControl is
     * null
     * @throws IllegalStateException if the currentControl is not contained in
     * the form
     */
    @Override
    public Control replace(Control currentControl, Control newControl) {
        // Current and new control is the same instance - exit early
        if (currentControl == newControl) {
            return newControl;
        }

        int controlIndex = getControls().indexOf(currentControl);
        Control result = ContainerUtils.replace(this, currentControl, newControl, controlIndex, getControlMap());

        if (newControl instanceof Field) {
            Field field = (Field) newControl;

            if (field instanceof Button) {
                // Replace field in buttonList for fast access
                int buttonIndex = getButtonList().indexOf(currentControl);
                getButtonList().set(buttonIndex, (Button) field);

            } else {
                // Replace field in fieldList for fast access
                int fieldIndex = getFieldList().indexOf(currentControl);
                getFieldList().set(fieldIndex, field);
            }

            // Set parent form
            field.setForm(this);

            if (currentControl instanceof Field) {
                // Remove form reference from current control
                ((Field) currentControl).setForm(null);
            }

            if (getDefaultFieldSize() > 0) {
                if (field instanceof TextField) {
                    ((TextField) field).setSize(getDefaultFieldSize());

                } else if (field instanceof FileField) {
                    ((FileField) field).setSize(getDefaultFieldSize());

                } else if (field instanceof TextArea) {
                    ((TextArea) field).setCols(getDefaultFieldSize());
                }
            }
        }

        return result;
    }

    /**
     * @see Container#remove(org.apache.click.Control)
     *
     * @param control the control to remove from the container
     * @return true if the control was removed from the container
     * @throws IllegalArgumentException if the control is null
     */
    @Override
    public boolean remove(Control control) {

        boolean removed = super.remove(control);

        if (removed && control instanceof Field) {
            Field field = (Field) control;

            field.setForm(null);

            if (field instanceof Button) {
                getButtonList().remove(field);

            } else {
                getFieldList().remove(field);
            }
        }
        getFieldWidths().remove(control.getName());

        return removed;
    }

    /**
     * Remove the named field from the form, returning true if removed
     * or false if not found.
     *
     * @param name the name of the field to remove from the form
     * @return true if the named field was removed or false otherwise
     */
    public boolean removeField(String name) {
        Control control = getControl(name);

        if (control != null) {
            return remove(control);

        } else {
            return false;
        }
    }

    /**
     * Remove the list of named fields from the form.
     *
     * @param fieldNames the list of field names to remove from the form
     * @throws IllegalArgumentException if any of the fields is null
     */
    public void removeFields(List<String> fieldNames) {
        if (fieldNames != null) {
            for (int i = 0; i < fieldNames.size(); i++) {
                removeField(fieldNames.get(i).toString());
            }
        }
    }

    // Public Attributes ------------------------------------------------------

    /**
     * Return the form's html tag: <tt>form</tt>.
     *
     * @see AbstractControl#getTag()
     *
     * @return this controls html tag
     */
    @Override
    public String getTag() {
        return "form";
    }

    /**
     * Return the form "action" attribute URL value. If the action URL attribute
     * has not been explicitly set the form action attribute will target the
     * page containing the form. This is the default behaviour for most scenarios.
     * However if you explicitly specify the form "action" URL attribute, this
     * value will be used instead.
     * <p/>
     * Setting the form action attribute is useful for situations where you want
     * a form to submit to a different page. This can also be used to have a
     * form submit to the J2EE Container for authentication, by setting the
     * action URL to "<tt>j_security_check</tt>".
     * <p/>
     * The action URL will always be encoded by the response to ensure it includes
     * the Session ID if required.
     *
     * @return the form "action" attribute URL value.
     */
    public String getActionURL() {
        Context context = getContext();
        HttpServletResponse response = context.getResponse();
        if (actionURL == null) {
            HttpServletRequest request = context.getRequest();
            return response.encodeURL(ClickUtils.getRequestURI(request));

        } else {
            return response.encodeURL(actionURL);
        }
    }

    /**
     * Return the form "action" attribute URL value. By setting this value you
     * will override the default action URL which points to the page containing
     * the form.
     * <p/>
     * Setting the form action attribute is useful for situations where you want
     * a form to submit to a different page. This can also be used to have a
     * form submit to the J2EE Container for authentication, by setting the
     * action URL to "<tt>j_security_check</tt>".
     *
     * @param value the form "action" attribute URL value
     */
    public void setActionURL(String value) {
        this.actionURL = value;
    }

    /**
     * @see AbstractControl#getControlSizeEst()
     *
     * @return the estimated rendered control size in characters
     */
    @Override
    public int getControlSizeEst() {
        return 400 + (getControls().size() * 350);
    }

    /**
     * Return true if the form is a disabled.
     *
     * @return true if the form is a disabled
     */
    public boolean isDisabled() {
        return disabled;
    }

    /**
     * Set the form disabled flag.
     *
     * @param disabled the form disabled flag
     */
    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }

    /**
     * Return the form "enctype" attribute value, or null if not defined.
     *
     * @return the form "enctype" attribute value, or null if not defined
     */
    public String getEnctype() {
        if (enctype == null) {
            for (Field field : ContainerUtils.getInputFields(this)) {
                if (!field.isHidden() && (field instanceof FileField)) {
                    enctype = MULTIPART_FORM_DATA;
                    break;
                }
            }
        }
        return enctype;
    }

    /**
     * Set the form "enctype" attribute value.
     *
     * @param enctype the form "enctype" attribute value, or null if not defined
     */
    public void setEnctype(String enctype) {
        this.enctype = enctype;
    }

    /**
     * Return the form level error message.
     *
     * @return the form level error message
     */
    public String getError() {
        return error;
    }

    /**
     * Set the form level validation error message. If the error message is not
     * null the form is invalid.
     *
     * @param error the validation error message
     */
    public void setError(String error) {
        this.error = error;
    }

    /**
     * Return a list of form fields which are not valid, not hidden and not
     * disabled.
     *
     * @return list of form fields which are not valid, not hidden and not
     *  disabled
     */
    public List<Field> getErrorFields() {
        return ContainerUtils.getErrorFields(this);
    }

    /**
     * Return the named field if contained in the form or null if not found.
     *
     * @param name the name of the field
     * @return the named field if contained in the form
     *
     * @throws IllegalStateException if a non-field control is found with the
     * specified name
     */
    public Field getField(String name) {
        Control control = ContainerUtils.findControlByName(this, name);

        if (control != null && !(control instanceof Field)) {
            throw new IllegalStateException("The control named " + name + " is an instance of the class "
                    + control.getClass().getName() + ", which is not a " + Field.class.getName() + " subclass.");
        }
        return (Field) control;
    }

    /**
     * Return the field value for the named field, or null if the field is not
     * found.
     *
     * @param name the name of the field
     * @return the field value for the named field
     */
    public String getFieldValue(String name) {
        Field field = getField(name);

        if (field != null) {
            return field.getValue();

        } else {
            return null;
        }
    }

    /**
     * Return the Form HEAD elements to be included in the page.
     * The following resources are returned:
     *
     * <ul>
     * <li><tt>click/control.css</tt></li>
     * <li><tt>click/control.js</tt></li>
     * </ul>
     *
     * @see org.apache.click.Control#getHeadElements()
     *
     * @return the form list of HEAD elements to be included in the page
     */
    @Override
    public List<Element> getHeadElements() {
        if (headElements == null) {
            headElements = super.getHeadElements();

            Context context = getContext();
            String versionIndicator = ClickUtils.getResourceVersionIndicator(context);

            headElements.add(new CssImport("/click/control.css", versionIndicator));
            headElements.add(new JsImport("/click/control.js", versionIndicator));
        }
        return headElements;
    }

    /**
     * Return the form method <tt>["post" | "get"]</tt>, default value is
     * <tt>post</tt>.
     *
     * @return the form method
     */
    public String getMethod() {
        return method;
    }

    /**
     * Set the form method <tt>["post" | "get"]</tt>.
     *
     * @param value the form method
     */
    public void setMethod(String value) {
        method = value;
    }

    /**
     * Return true if the page request is a submission from this form.
     * <p/>
     * A form submission requires the following criteria:
     * <ul>
     *   <li>the Form name must be present as a request parameter (Form
     *   automatically adds a HiddenField which value is set to the Form name.
     *   This ensures the Form name is present when submitting the form)</li>
     *   <li>the request method must equal the Form {@link #method}, for example
     *   both must be <tt>GET</tt> or <tt>POST</tt></li>
     * </ul>
     *
     * @return true if the page request is a submission from this form
     */
    public boolean isFormSubmission() {
        if (formSubmission == null) {
            Context context = getContext();
            String requestMethod = context.getRequest().getMethod();

            if (!getMethod().equalsIgnoreCase(requestMethod)) {
                return false;
            }

            formSubmission = getName().equals(context.getRequestParameter(FORM_NAME));
        }
        return formSubmission;
    }

    /**
     * Set the name of the form.
     *
     * @see org.apache.click.Control#setName(String)
     *
     * @param name of the control
     * @throws IllegalArgumentException if the name is null
     */
    @Override
    public void setName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Null name parameter");
        }
        this.name = name;

        // TODO: Remove with stateful pages
        HiddenField nameField = (HiddenField) getField(FORM_NAME);
        if (nameField == null) {
            // Create a hidden field that won't be processed and name cannot change
            nameField = new NonProcessedHiddenField(FORM_NAME, String.class);
            add(nameField);
            insertIndexOffset++;
        }
        nameField.setValue(name);
    }

    /**
     * Return true if the form is a readonly.
     *
     * @return true if the form is a readonly
     */
    public boolean isReadonly() {
        return readonly;
    }

    /**
     * Set the form readonly flag.
     *
     * @param readonly the form readonly flag
     */
    public void setReadonly(boolean readonly) {
        this.readonly = readonly;
    }

    /**
     * Return true if the fields are valid and there is no form level error,
     * otherwise return false.
     *
     * @return true if the fields are valid and there is no form level error
     */
    public boolean isValid() {
        if (getError() != null) {
            return false;
        }

        for (Field field : ContainerUtils.getInputFields(this)) {
            if (!field.isValid()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Return the ordered list of form fields, excluding buttons.
     * <p/>
     * The order of the fields is the same order they were added to the form.
     * <p/>
     * The returned list includes only fields added directly to the Form.
     *
     * @return the ordered List of form fields, excluding buttons
     */
    public List<Field> getFieldList() {
        return fieldList;
    }

    /**
     * Return the Map of form fields (including buttons), keyed
     * on field name.
     * <p/>
     * The returned map includes only fields added directly to the Form.
     *
     * @see #getControlMap()
     *
     * @return the Map of form fields (including buttons), keyed
     * on field name
     */
    public Map<String, Control> getFields() {
        return getControlMap();
    }

    /**
     * Return true if the Form fields should validate themselves when being
     * processed.
     *
     * @return true if the form fields should perform validation when being
     *  processed
     */
    public boolean getValidate() {
        return validate;
    }

    /**
     * Set the Form field validation flag, telling the Fields to validate
     * themselves when their <tt>onProcess()</tt> method is invoked.
     *
     * @param validate the Form field validation flag
     */
    public void setValidate(boolean validate) {
        this.validate = validate;
    }

    /**
     * Return the buttons &lt;td&gt; HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     *
     * @return the field label HTML horizontal alignment
     */
    public String getButtonAlign() {
        return buttonAlign;
    }

    /**
     * Set the button &lt;td&gt; HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     * Note the given align is not validated.
     *
     * @param align the field label HTML horizontal alignment
     */
    public void setButtonAlign(String align) {
        buttonAlign = align;
    }

    /**
     * Return the ordered list of {@link Button}s.
     * <p/>
     * The order of the buttons is the same order they were added to the form.
     * <p/>
     * The returned list includes only buttons added directly to the Form.
     *
     * @return the ordered list of {@link Button}s.
     */
    public List<Button> getButtonList() {
        return buttonList;
    }

    /**
     * Return the button &lt;td&gt; "style" attribute value.
     *
     * @return the button &lt;td&gt; "style" attribute value
     */
    public String getButtonStyle() {
        return buttonStyle;
    }

    /**
     * Set the button &lt;td&gt; "style" attribute value.
     *
     * @param value the button &lt;td&gt; "style" attribute value
     */
    public void setButtonStyle(String value) {
        this.buttonStyle = value;
    }

    /**
     * Return the number of form layout table columns. This property is used to
     * layout the number of table columns the form is rendered with.
     *
     * @return the number of form layout table columns
     */
    public int getColumns() {
        return columns;
    }

    /**
     * Set the number of form layout table columns. This property is used to
     * layout the number of table columns the form is rendered with.
     *
     * @param columns the number of form layout table columns
     */
    public void setColumns(int columns) {
        this.columns = columns;
    }

    /**
     * Return the form default field size. If the form default field size is
     * greater than 0, when fields are added to the form the field's size will
     * be set to the default value.
     *
     * @return the form default field size
     */
    public int getDefaultFieldSize() {
        return defaultFieldSize;
    }

    /**
     * Return the form default field size. If the form default field size is
     * greater than 0, when fields are added to the form the field's size will
     * be set to the default value.
     *
     * @param size the default field size
     */
    public void setDefaultFieldSize(int size) {
        this.defaultFieldSize = size;
    }

    /**
     * Return the errors block HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     *
     * @return the errors block HTML horizontal alignment
     */
    public String getErrorsAlign() {
        return errorsAlign;
    }

    /**
     * Set the errors block HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     * Note the given align is not validated.
     *
     * @param align the errors block HTML horizontal alignment
     */
    public void setErrorsAlign(String align) {
        errorsAlign = align;
    }

    /**
     * Return the form errors position <tt>["top", "middle", "bottom"]</tt>.
     *
     * @return form errors position
     */
    public String getErrorsPosition() {
        return errorsPosition;
    }

    /**
     * Set the form errors position <tt>["top", "middle", "bottom"]</tt>.
     *
     * @param position the form errors position
     */
    public void setErrorsPosition(String position) {
        if (POSITION_TOP.equals(position) || POSITION_MIDDLE.equals(position) || POSITION_BOTTOM.equals(position)) {

            errorsPosition = position;

        } else {
            throw new IllegalArgumentException("Invalid position: " + position);
        }
    }

    /**
     * Return the error &lt;td&gt; "style" attribute value.
     *
     * @return the error &lt;td&gt; "style" attribute value
     */
    public String getErrorsStyle() {
        return errorsStyle;
    }

    /**
     * Set the errors &lt;td&gt; "style" attribute value.
     *
     * @param value the errors &lt;td&gt; "style" attribute value
     */
    public void setErrorsStyle(String value) {
        this.errorsStyle = value;
    }

    /**
     * Return the field &lt;td&gt; "style" attribute value.
     *
     * @return the field &lt;td&gt; "style" attribute value
     */
    public String getFieldStyle() {
        return fieldStyle;
    }

    /**
     * Set the field &lt;td&gt; "style" attribute value. Fields can override
     * this value by providing a {@link Field#setParentStyleHint(java.lang.String)}.
     *
     * @see Field#setParentStyleHint(java.lang.String)
     * @see Field#setParentStyleClassHint(java.lang.String)
     *
     * @param value the field &lt;td&gt; "style" attribute value
     */
    public void setFieldStyle(String value) {
        this.fieldStyle = value;
    }

    /**
     * Return the map of field width values, keyed on field name.
     *
     * @return the map of field width values, keyed on field name
     */
    public Map<String, Integer> getFieldWidths() {
        return fieldWidths;
    }

    /**
     * Return true if JavaScript client side form validation is enabled.
     *
     * @return true if JavaScript client side form validation is enabled
     */
    public boolean isJavaScriptValidation() {
        return javaScriptValidation;
    }

    /**
     * Return true if JavaScript client side form validation is enabled.
     *
     * @deprecated use {@link #isJavaScriptValidation()} instead
     *
     * @return true if JavaScript client side form validation is enabled
     */
    public boolean getJavaScriptValidation() {
        return javaScriptValidation;
    }

    /**
     * Set the JavaScript client side form validation flag.
     *
     * @param validate the JavaScript client side validation flag
     */
    public void setJavaScriptValidation(boolean validate) {
        javaScriptValidation = validate;
    }

    /**
     * Return the field label HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     *
     * @return the field label HTML horizontal alignment
     */
    public String getLabelAlign() {
        return labelAlign;
    }

    /**
     * Set the field label HTML horizontal alignment: "<tt>left</tt>",
     * "<tt>center</tt>", "<tt>right</tt>".
     * Note the given align is not validated.
     *
     * @param align the field label HTML horizontal alignment
     */
    public void setLabelAlign(String align) {
        labelAlign = align;
    }

    /**
     * Return the form labels position <tt>["left", "top"]</tt>.
     *
     * @return form labels position
     */
    public String getLabelsPosition() {
        return labelsPosition;
    }

    /**
     * Set the form labels position <tt>["left", "top"]</tt>.
     *
     * @param position the form labels position
     */
    public void setLabelsPosition(String position) {
        if (POSITION_LEFT.equals(position) || POSITION_TOP.equals(position)) {
            labelsPosition = position;
        } else {
            throw new IllegalArgumentException("Invalid position: " + position);
        }
    }

    /**
     * Return the label &lt;td&gt; "style" attribute value.
     *
     * @return the label &lt;td&gt; "style" attribute value
     */
    public String getLabelStyle() {
        return labelStyle;
    }

    /**
     * Set the label &lt;td&gt; "style" attribute value.
     * <p/>
     * This value can be overridden by Fields through their
     * {@link Field#setParentStyleHint(java.lang.String)} property.
     *
     * @param value the label &lt;td&gt; "style" attribute value
     */
    public void setLabelStyle(String value) {
        this.labelStyle = value;
    }

    /**
     * The callback listener will only be called during processing if the field
     * value is valid. If the field has validation errors the listener will not
     * be called.
     *
     * @see org.apache.click.Control#setListener(Object, String)
     *
     * @param listener the listener object with the named method to invoke
     * @param method the name of the method to invoke
     */
    @Override
    public void setListener(Object listener, String method) {
        super.setListener(listener, method);
    }

    // Public Methods ---------------------------------------------------------

    /**
     * Clear any form or field errors by setting them to null.
     */
    public void clearErrors() {
        setError(null);

        for (Field field : ContainerUtils.getInputFields(this)) {
            field.setError(null);
        }
    }

    /**
     * Clear all the form field values setting them to null.
     */
    public void clearValues() {
        for (Field field : ContainerUtils.getInputFields(this)) {
            if (!field.getName().equals(FORM_NAME) && (!field.getName().startsWith(SUBMIT_CHECK))) {
                field.setValue(null);
            }
        }
    }

    /**
     * Copy the given object's attributes into the Form's field values. In
     * other words automatically populate Form's field values with the
     * given objects attributes.
     * <p/>
     * The following example populates the Form field with Customer
     * attributes:
     *
     * <pre class="prettyprint">
     * public void onGet() {
     *     Long customerId = ..
     *     Customer customer = CustomerDAO.findByPK(customerId);
     *     form.copyFrom(customer);
     * } </pre>
     *
     * copyForm also supports <tt>java.util.Map</tt> as an argument.
     * <p/>
     * By specifying a map, the Form's field values will be populated by
     * matching key/value pairs. A match occurs when the map's key is equal to
     * a field's name.
     * <p/>
     * The following example populates the Form fields with a map's
     * key/value pairs:
     *
     * <pre class="prettyprint">
     * public void onInit() {
     *     form = new Form("form");
     *     form.add(new TextField("name"));
     *     form.add(new TextField("address.street"));
     * }
     *
     * public void onGet() {
     *     Map map = new HashMap();
     *     map.put("name", "Steve");
     *     map.put("address.street", "12 Long street");
     *     form.copyFrom(map);
     * } </pre>
     *
     * For more information on how Fields and Objects are copied see
     * {@link org.apache.click.util.ContainerUtils#copyObjectToContainer(java.lang.Object, org.apache.click.control.Container)}.
     *
     * @param object the object to obtain attribute values from
     * @throws IllegalArgumentException if the object parameter is null
     */
    public void copyFrom(Object object) {
        ContainerUtils.copyObjectToContainer(object, this);
    }

    /**
     * Copy the given object's attributes into the Form's field values. In other
     * words automatically populate Forms field values with the given objects
     * attributes. copyFrom also supports <tt>java.util.Map</tt> as an argument.
     * <p/>
     * If the debug parameter is true, debugging messages will be
     * logged.
     *
     * @see #copyFrom(java.lang.Object)
     *
     * @param object the object to obtain attribute values from
     * @param debug log debug statements when populating the form
     * @throws IllegalArgumentException if the object parameter is null
     */
    public void copyFrom(Object object, boolean debug) {
        ContainerUtils.copyObjectToContainer(object, this);
    }

    /**
     * Copy the Form's field values into the given object's attributes. In
     * other words automatically populate Object attributes with the Form's
     * field values.
     * <p/>
     * The following example populates the Customer attributes with the
     * Form's field values:
     *
     * <pre class="prettyprint">
     * public void onPost() {
     *     if (form.isValid()) {
     *         Customer customer = new Customer();
     *         form.copyTo(customer);
     *         ..
     *     }
     *     return true;
     * } </pre>
     *
     * copyTo also supports <tt>java.util.Map</tt> as an argument.
     * <p/>
     * By specifying a map, the map's key/value pairs are populated from
     * matching Form field names. A match occurs when a field's name is
     * equal to a map's key.
     * <p/>
     * The following example populates the map with the Form field values:
     *
     * <pre class="prettyprint">
     * public void onInit() {
     *     form = new Form("form");
     *     form.add(new TextField("name"));
     *     form.add(new TextField("address.street"));
     * }
     *
     * public void onGet() {
     *     Map map = new HashMap();
     *     map.put("name", null);
     *     map.put("address.street", null);
     *     form.copyTo(map);
     * } </pre>
     *
     * Note that the map acts as a template to specify which fields to populate
     * from.
     *
     * For more information on how Fields and Objects are copied see
     * {@link org.apache.click.util.ContainerUtils#copyContainerToObject(org.apache.click.control.Container, java.lang.Object)}.
     *
     * @param object the object to populate with field values
     * @throws IllegalArgumentException if the object parameter is null
     */
    public void copyTo(Object object) {
        ContainerUtils.copyContainerToObject(this, object);
    }

    /**
     * Copy the Form's field values into the given object's attributes. In other
     * words automatically populate Object attributes with the Forms field
     * values. copyTo also supports <tt>java.util.Map</tt> as an argument.
     * <p/>
     * If the debug parameter is true, debugging messages will be
     * logged.
     *
     * @see #copyTo(java.lang.Object)
     *
     * @param object the object to populate with field values
     * @param debug log debug statements when populating the object
     * @throws IllegalArgumentException if the object parameter is null
     */
    public void copyTo(Object object, boolean debug) {
        ContainerUtils.copyContainerToObject(this, object);
    }

    /**
     * Return the form state. The following state is returned:
     *
     * <ul>
     * <li>all the input Field values and FieldSets contained in the Form and
     * child containers.</li>
     * </ul>
     *
     * @return the state of input Fields and FieldSets contained in the form
     */
    public Object getState() {
        List<Field> fields = new ArrayList<Field>();
        addStatefulFields(this, fields);
        Map<String, Object> stateMap = new HashMap<String, Object>();
        for (Field field : fields) {
            Object state = field.getState();
            if (state != null) {
                stateMap.put(field.getName(), state);
            }
        }

        if (stateMap.isEmpty()) {
            return null;
        }
        return stateMap;
    }

    /**
     * Set the Form state. The state will be applied to all the input Fields
     * and FieldSets contained in the Form or child containers.
     *
     * @param state the Form state to set
     */
    public void setState(Object state) {
        if (state == null) {
            return;
        }

        Map stateMap = (Map) state;
        List<Field> fields = new ArrayList<Field>();
        addStatefulFields(this, fields);

        for (Field field : fields) {
            String fieldName = field.getName();
            if (stateMap.containsKey(fieldName)) {
                Object fieldState = stateMap.get(fieldName);
                field.setState(fieldState);
            }
        }
    }

    /**
     * Process the Form and its child controls only if the Form was submitted
     * by the user.
     * <p/>
     * This method invokes {@link #isFormSubmission()} to check whether the form
     * was submitted or not.
     * <p/>
     * The Forms processing order is:
     * <ol>
     * <li>All {@link Field} controls in the order they were added</li>
     * <li>All {@link Button} controls in the order they were added</li>
     * <li>Invoke the Forms listener if defined</li>
     * </ol>
     *
     * This method delegates validation to {@link #validate()} while
     * file upload validation are delegated to {@link #validateFileUpload()}.
     *
     * @see org.apache.click.Context#getRequestParameter(String)
     * @see org.apache.click.Context#getFileItemMap()
     *
     * @return true to continue Page event processing or false otherwise
     */
    @Override
    public boolean onProcess() {

        validateFileUpload();

        // If a POST error occurred exit early.
        if (hasPostError()) {
            // Remove exception to ensure other forms on Page do not
            // validate twice for same error.
            getContext().getRequest().removeAttribute(FileUploadService.UPLOAD_EXCEPTION);

            return true;
        }

        boolean continueProcessing = true;
        if (isFormSubmission()) {

            for (int i = 0, size = getControls().size(); i < size; i++) {
                Control control = getControls().get(i);
                String controlName = control.getName();
                if (controlName == null || !controlName.startsWith(Form.SUBMIT_CHECK)) {

                    if (!control.onProcess()) {
                        continueProcessing = false;
                    }
                }
            }

            if (getValidate()) {
                validate();
            }

            dispatchActionEvent();
        }

        return continueProcessing;
    }

    /**
     * Destroy the controls contained in the Form and clear any form
     * error message.
     *
     * @see org.apache.click.Control#onDestroy()
     */
    @Override
    public void onDestroy() {
        super.onDestroy();

        formSubmission = null;
        setError(null);
    }

    /**
     * The validate method is invoked by {@link #onProcess()} to validate
     * the request submission. A Form subclass can override this method
     * to implement cross-field validation logic.
     * <p/>
     * If the Form determines that the submission is invalid it should set the
     * {@link #error} property with an appropriate error message. For example:
     *
     * <pre class="prettyprint">
     * public class RegistrationForm extends Form {
     *
     *     // Add validation to ensure the password and confirmPassword fields match
     *     public void validate() {
     *         String password = getFieldValue("password");
     *         String confirmPassword = getFieldValue("confirmPassword");
     *         if (!password.equals(confirmPassword)) {
     *
     *             // Set Form's error property value that will be shown to the user
     *             setError("The passwords do not match.");
     *         }
     *     }
     * } </pre>
     */
    public void validate() {
    }

    /**
     * Perform a form submission check ensuring the user has not replayed the
     * form submission by using the browser's back or refresh buttons or by
     * clicking the Form submit button twice, in quick succession. If the form
     * submit is valid this method will return true, otherwise set the page to
     * redirect to the given redirectPath and return false.
     * <p/>
     * This method will add a token to the user's session and a hidden field
     * to the form to validate future submits.
     * <p/>
     * Form submit checks should be performed before the pages controls are
     * processed in the Page onSecurityCheck method. For example:
     *
     * <pre class="prettyprint">
     * public class Order extends Page {
     *     ..
     *
     *     public boolean onSecurityCheck() {
     *         return form.onSubmitCheck(this, "/invalid-submit.html");
     *     }
     * } </pre>
     *
     * Form submit checks should generally be combined with the Post-Redirect
     * pattern which provides a better user experience when pages are refreshed.
     * <p/>
     * <b>Please note:</b> a call to onSubmitCheck always succeeds for Ajax
     * requests.
     *
     * @param page the page invoking the Form submit check
     * @param redirectPath the path to redirect invalid submissions to
     * @return true if the form submit is OK or false otherwise
     * @throws IllegalArgumentException if the page or redirectPath is null
     */
    public boolean onSubmitCheck(Page page, String redirectPath) {
        if (page == null) {
            throw new IllegalArgumentException("Null page parameter");
        }
        if (redirectPath == null) {
            throw new IllegalArgumentException("Null redirectPath parameter");
        }

        if (performSubmitCheck()) {
            return true;

        } else {
            page.setRedirect(redirectPath);

            return false;
        }
    }

    /**
     * Perform a form submission check ensuring the user has not replayed the
     * form submission by using the browser back button. If the form submit
     * is valid this method will return true, otherwise set the page to
     * redirect to the given Page class and return false.
     * <p/>
     * This method will add a token to the user's session and a hidden field
     * to the form to validate future submits.
     * <p/>
     * Form submit checks should be performed before the pages controls are
     * processed in the Page onSecurityCheck method. For example:
     *
     * <pre class="prettyprint">
     * public class Order extends Page {
     *     ..
     *
     *     public boolean onSecurityCheck() {
     *         return form.onSubmitCheck(this, InvalidSubmitPage.class);
     *     }
     * } </pre>
     *
     * Form submit checks should generally be combined with the Post-Redirect
     * pattern which provides a better user experience when pages are refreshed.
     * <p/>
     * <b>Please note:</b> a call to onSubmitCheck always succeeds for Ajax
     * requests.
     *
     * @param page the page invoking the Form submit check
     * @param pageClass the page class to redirect invalid submissions to
     * @return true if the form submit is OK or false otherwise
     * @throws IllegalArgumentException if the page or pageClass is null
     */
    public boolean onSubmitCheck(Page page, Class<? extends Page> pageClass) {
        if (page == null) {
            throw new IllegalArgumentException("Null page parameter");
        }
        if (pageClass == null) {
            throw new IllegalArgumentException("Null pageClass parameter");
        }

        if (performSubmitCheck()) {
            return true;

        } else {
            page.setRedirect(pageClass);

            return false;
        }
    }

    /**
     * Perform a form submission check ensuring the user has not replayed the
     * form submission by using the browser back button. If the form submit
     * is valid this method will return true, otherwise the given listener
     * object and method will be invoked.
     * <p/>
     * This method will add a token to the users session and a hidden field
     * to the form to validate future submit's.
     * <p/>
     * Form submit checks should be performed before the pages controls are
     * processed in the Page onSecurityCheck method. For example:
     *
     * <pre class="prettyprint">
     * public class Order extends Page {
     *     ..
     *
     *     public boolean onSecurityCheck() {
     *         return form.onSubmitCheck(his, this, "onInvalidSubmit");
     *     }
     *
     *     public boolean onInvalidSubmit() {
     *        getContext().setRequestAttribute("invalidPath", getPath());
     *        setForward("invalid-submit.htm");
     *        return false;
     *     }
     * } </pre>
     *
     * Form submit checks should generally be combined with the Post-Redirect
     * pattern which provides a better user experience when pages are refreshed.
     * <p/>
     * <b>Please note:</b> a call to onSubmitCheck always succeeds for Ajax
     * requests.
     *
     * @param page the page invoking the Form submit check
     * @param submitListener the listener object to call when an invalid submit
     *      occurs
     * @param submitListenerMethod the listener method to invoke when an
     *      invalid submit occurs
     * @return true if the form submit is valid, or the return value of the
     *      listener method otherwise
     * @throws IllegalArgumentException if the page, submitListener or
     *      submitListenerMethod is null
     */
    public boolean onSubmitCheck(Page page, Object submitListener, String submitListenerMethod) {

        if (page == null) {
            throw new IllegalArgumentException("Null page parameter");
        }
        if (submitListener == null) {
            throw new IllegalArgumentException("Null submitListener parameter");
        }
        if (submitListenerMethod == null) {
            String msg = "Null submitListenerMethod parameter";
            throw new IllegalArgumentException(msg);
        }

        if (performSubmitCheck()) {
            return true;

        } else {
            return ClickUtils.invokeListener(submitListener, submitListenerMethod);
        }
    }

    /**
     * Remove the Form state from the session for the given request context.
     *
     * @see #saveState(org.apache.click.Context)
     * @see #restoreState(org.apache.click.Context)
     *
     * @param context the request context
     */
    public void removeState(Context context) {
        ClickUtils.removeState(this, getName(), context);
    }

    /**
     * Restore the Form state from the session for the given request context.
     * <p/>
     * This method delegates to {@link #setState(java.lang.Object)} to set the
     * form restored state.
     *
     * @see #saveState(org.apache.click.Context)
     * @see #removeState(org.apache.click.Context)
     *
     * @param context the request context
     */
    public void restoreState(Context context) {
        ClickUtils.restoreState(this, getName(), context);
    }

    /**
     * Save the Form state to the session for the given request context.
     * <p/>
     * * This method delegates to {@link #getState()} to retrieve the form state
     * to save.
     *
     * @see #restoreState(org.apache.click.Context)
     * @see #removeState(org.apache.click.Context)
     *
     * @param context the request context
     */
    public void saveState(Context context) {
        ClickUtils.saveState(this, getName(), context);
    }

    /**
     * Return the rendered opening form tag and all the forms hidden fields.
     *
     * @return the rendered form start tag and the forms hidden fields
     */
    public String startTag() {
        List<Field> formFields = ContainerUtils.getInputFields(this);

        int bufferSize = getFormSizeEst(formFields);

        HtmlStringBuffer buffer = new HtmlStringBuffer(bufferSize);

        renderHeader(buffer, formFields);

        return buffer.toString();
    }

    /**
     * Return the rendered form end tag and JavaScript for field focus
     * and validation.
     *
     * @return the rendered form end tag
     */
    public String endTag() {
        HtmlStringBuffer buffer = new HtmlStringBuffer();

        List<Field> formFields = ContainerUtils.getInputFields(this);

        renderTagEnd(formFields, buffer);

        return buffer.toString();
    }

    /**
     * Render the HTML representation of the Form.
     * <p/>
     * If the form contains errors after processing, these errors will be
     * rendered.
     *
     * @see #toString()
     *
     * @param buffer the specified buffer to render the control's output to
     */
    @Override
    public void render(HtmlStringBuffer buffer) {
        final boolean process = getContext().getRequest().getMethod().equalsIgnoreCase(getMethod());

        List<Field> formFields = ContainerUtils.getInputFields(this);

        renderHeader(buffer, formFields);

        buffer.append("<table class=\"form\" id=\"");
        buffer.append(getId());
        buffer.append("-form\"><tbody>\n");

        // Render fields, errors and buttons
        if (POSITION_TOP.equals(getErrorsPosition())) {
            renderErrors(buffer, process);
            renderFields(buffer);
            renderButtons(buffer);

        } else if (POSITION_MIDDLE.equals(getErrorsPosition())) {
            renderFields(buffer);
            renderErrors(buffer, process);
            renderButtons(buffer);

        } else if (POSITION_BOTTOM.equals(getErrorsPosition())) {
            renderFields(buffer);
            renderButtons(buffer);
            renderErrors(buffer, process);

        } else {
            String msg = "Invalid errorsPosition:" + getErrorsPosition();
            throw new IllegalArgumentException(msg);
        }

        buffer.append("</tbody></table>\n");

        renderTagEnd(formFields, buffer);
    }

    // Protected Methods ------------------------------------------------------

    /**
     * Perform a back button submit check, returning true if the request is
     * valid or false otherwise. This method will add a submit check token
     * to the form as a hidden field, and to the session.
     *
     * @return true if the submit is OK or false otherwise
     */
    protected boolean performSubmitCheck() {

        if (StringUtils.isBlank(getName())) {
            throw new IllegalStateException("Form name is not defined.");
        }

        // CLK-333. Don't regenerate submit tokens for Ajax requests.
        Context context = getContext();
        if (context.isAjaxRequest()) {
            return true;
        }

        String resourcePath = context.getResourcePath();
        int slashIndex = resourcePath.indexOf('/');
        if (slashIndex != -1) {
            resourcePath = resourcePath.replace('/', '_');
        }

        // Ensure resourcePath starts with a '_' separator. If slashIndex == -1
        // or slashIndex > 0, resourcePath does not start with slash.
        if (slashIndex != 0) {
            resourcePath = '_' + resourcePath;
        }

        final HttpServletRequest request = context.getRequest();
        final String submitTokenName = SUBMIT_CHECK + getName() + resourcePath;

        boolean isValidSubmit = true;

        // If not this form exit
        String formName = context.getRequestParameter(FORM_NAME);

        // Only test if submit for this form
        if (!context.isForward() && request.getMethod().equalsIgnoreCase(getMethod())
                && getName().equals(formName)) {

            Long sessionTime = (Long) context.getSessionAttribute(submitTokenName);

            if (sessionTime != null) {
                String value = context.getRequestParameter(submitTokenName);
                if (value == null || value.length() == 0) {
                    // CLK-289. If a session attribute exists for the
                    // SUBMIT_CHECK, but no request parameter, we assume the
                    // submission is a duplicate and therefore invalid.
                    LogService logService = ClickUtils.getLogService();
                    logService.warn("    'Redirect After Post' token called '" + submitTokenName
                            + "' is registered in the session, " + "but no matching request parameter was found. "
                            + "(form name: '" + getName() + "'). To protect against a 'duplicate post', "
                            + "Form.onSubmitCheck() will return false.");
                    isValidSubmit = false;
                } else {
                    Long formTime = Long.valueOf(value);
                    isValidSubmit = formTime.equals(sessionTime);
                }
            }
        }

        // CLK-267: check against adding a duplicate field
        HiddenField field = (HiddenField) getField(submitTokenName);
        if (field == null) {
            field = new NonProcessedHiddenField(submitTokenName, Long.class);
            add(field);
            insertIndexOffset++;
        }

        // Save state info to form and session
        final Long time = System.currentTimeMillis();
        field.setValueObject(time);

        context.setSessionAttribute(submitTokenName, time);

        if (isValidSubmit) {
            return true;

        } else {
            return false;
        }
    }

    /**
     * Return the estimated rendered form size in characters.
     *
     * @param formFields the list of form fields
     * @return the estimated rendered form size in characters
     */
    protected int getFormSizeEst(List<Field> formFields) {
        return 500 + (formFields.size() * 350);
    }

    /**
     * Render the given form start tag and the form hidden fields to the given
     * buffer.
     *
     * @param buffer the HTML string buffer to render to
     * @param formFields the list of form fields
     */
    protected void renderHeader(HtmlStringBuffer buffer, List<Field> formFields) {

        buffer.elementStart(getTag());

        buffer.appendAttribute("method", getMethod());
        buffer.appendAttribute("name", getName());
        buffer.appendAttribute("id", getId());
        buffer.appendAttribute("action", getActionURL());
        buffer.appendAttribute("enctype", getEnctype());

        appendAttributes(buffer);

        if (isJavaScriptValidation()) {
            String javaScript = "return on_" + getId() + "_submit();";
            buffer.appendAttribute("onsubmit", javaScript);
        }
        buffer.closeTag();
        buffer.append("\n");

        // Render hidden fields
        for (Field field : ContainerUtils.getHiddenFields(this)) {
            field.render(buffer);
            buffer.append("\n");
        }
    }

    /**
     * Render the non hidden Form Fields to the string buffer.
     * <p/>
     * This method delegates the rendering of the form fields to
     * {@link #renderControls(HtmlStringBuffer, Container, List, Map, int)}.
     *
     * @param buffer the StringBuffer to render to
     */
    protected void renderFields(HtmlStringBuffer buffer) {

        // If Form contains only the FORM_NAME HiddenField, exit early
        if (getControls().size() == 1) {

            // getControlMap is cheaper than getFieldMap, so check that first
            if (getControlMap().containsKey(FORM_NAME)) {
                return;

            } else {
                Map<String, Field> fieldMap = ContainerUtils.getFieldMap(this);
                if (fieldMap.containsKey(FORM_NAME)) {
                    return;
                }
            }
        }

        buffer.append("<tr><td>\n");

        renderControls(buffer, this, getControls(), getFieldWidths(), getColumns());
        buffer.append("</td></tr>\n");
    }

    /**
     * Render the specified controls of the container to the string buffer.
     * <p/>
     * fieldWidths is a map specifying the width for specific fields contained
     * in the list of controls. The fieldWidths map is keyed on field name.
     *
     * @param buffer the StringBuffer to render to
     * @param container the container which controls to render
     * @param controls the controls to render
     * @param fieldWidths a map of field widths keyed on field name
     * @param columns the number of form layout table columns
     */
    protected void renderControls(HtmlStringBuffer buffer, Container container, List<Control> controls,
            Map<String, Integer> fieldWidths, int columns) {

        buffer.append("<table class=\"fields\"");
        String containerId = container.getId();
        if (containerId != null) {
            buffer.appendAttribute("id", containerId + "-fields");
        }
        buffer.append("><tbody>\n");

        int column = 1;
        boolean openTableRow = false;

        for (Control control : controls) {

            // Buttons are rendered separately
            if (control instanceof Button) {
                continue;
            }

            if (!isHidden(control)) {

                // Control width
                Integer width = fieldWidths.get(control.getName());

                if (column == 1) {
                    buffer.append("<tr class=\"fields\">\n");
                    openTableRow = true;
                }

                if (control instanceof FieldSet) {
                    FieldSet fieldSet = (FieldSet) control;
                    buffer.append("<td class=\"fields");
                    String cellStyleClass = fieldSet.getParentStyleClassHint();
                    if (cellStyleClass != null) {
                        buffer.append(" ");
                        buffer.append(cellStyleClass);
                    }
                    buffer.append("\"");

                    buffer.appendAttribute("style", fieldSet.getParentStyleHint());

                    if (width != null) {
                        int colspan = (width.intValue() * 2);
                        buffer.appendAttribute("colspan", colspan);
                    } else {
                        buffer.appendAttribute("colspan", 2);
                    }

                    buffer.append(">\n");
                    control.render(buffer);
                    buffer.append("</td>\n");

                } else if (control instanceof Label) {
                    Label label = (Label) control;
                    buffer.append("<td align=\"");
                    buffer.append(getLabelAlign());
                    buffer.append("\" class=\"fields");

                    String cellStyleClass = label.getParentStyleClassHint();
                    if (cellStyleClass != null) {
                        buffer.append(" ");
                        buffer.append(cellStyleClass);
                    }
                    buffer.append("\"");

                    buffer.appendAttribute("style", label.getParentStyleHint());

                    if (width != null) {
                        int colspan = (width.intValue() * 2);
                        buffer.appendAttribute("colspan", colspan);
                    } else {
                        buffer.appendAttribute("colspan", 2);
                    }

                    if (label.hasAttributes()) {
                        Map<String, String> labelAttributes = label.getAttributes();
                        for (Map.Entry<String, String> entry : labelAttributes.entrySet()) {
                            String labelAttrName = entry.getKey();
                            if (!labelAttrName.equals("id") && !labelAttrName.equals("style")) {
                                buffer.appendAttributeEscaped(labelAttrName, entry.getValue());
                            }
                        }
                    }
                    buffer.append(">");
                    label.render(buffer);
                    buffer.append("</td>\n");

                } else if (control instanceof Field) {
                    Field field = (Field) control;
                    // Write out label
                    if (POSITION_LEFT.equals(getLabelsPosition())) {
                        buffer.append("<td class=\"fields");
                        String cellStyleClass = field.getParentStyleClassHint();
                        if (cellStyleClass != null) {
                            buffer.append(" ");
                            buffer.append(cellStyleClass);
                        }
                        buffer.append("\"");
                        buffer.appendAttribute("align", getLabelAlign());
                        String cellStyle = field.getParentStyleHint();
                        if (cellStyle == null) {
                            cellStyle = getLabelStyle();
                        }
                        buffer.appendAttribute("style", cellStyle);
                        buffer.append(">");
                    } else {
                        buffer.append("<td valign=\"top\" class=\"fields");
                        String cellStyleClass = field.getParentStyleClassHint();
                        if (cellStyleClass != null) {
                            buffer.append(" ");
                            buffer.append(cellStyleClass);
                        }
                        buffer.append("\"");
                        String cellStyle = field.getParentStyleHint();
                        if (cellStyle == null) {
                            cellStyle = getLabelStyle();
                        }
                        buffer.appendAttribute("style", cellStyle);
                        buffer.append(">");
                    }

                    // Store the field id and label (the values could be null)
                    String fieldId = field.getId();
                    String fieldLabel = field.getLabel();

                    // Only render a label if the fieldId and fieldLabel is set
                    if (fieldId != null && fieldLabel != null) {
                        if (field.isRequired()) {
                            buffer.append(getMessage("label-required-prefix"));
                        } else {
                            buffer.append(getMessage("label-not-required-prefix"));
                        }
                        buffer.elementStart("label");
                        buffer.appendAttribute("for", fieldId);
                        buffer.appendAttribute("style", field.getLabelStyle());
                        if (field.isDisabled()) {
                            buffer.appendAttributeDisabled();
                        }
                        String cellClass = field.getLabelStyleClass();
                        if (field.getError() == null) {
                            buffer.appendAttribute("class", cellClass);
                        } else {
                            buffer.append(" class=\"error");
                            if (cellClass != null) {
                                buffer.append(" ");
                                buffer.append(cellClass);
                            }
                            buffer.append("\"");
                        }
                        buffer.closeTag();
                        buffer.append(fieldLabel);
                        buffer.elementEnd("label");
                        if (field.isRequired()) {
                            buffer.append(getMessage("label-required-suffix"));
                        } else {
                            buffer.append(getMessage("label-not-required-suffix"));
                        }
                    }

                    if (POSITION_LEFT.equals(getLabelsPosition())) {
                        buffer.append("</td>\n");
                        buffer.append("<td");
                        buffer.appendAttribute("class", field.getParentStyleClassHint());
                        buffer.appendAttribute("align", "left");

                        String cellStyle = field.getParentStyleHint();
                        if (cellStyle == null) {
                            cellStyle = getFieldStyle();
                        }
                        buffer.appendAttribute("style", cellStyle);

                        if (width != null) {
                            int colspan = (width.intValue() * 2) - 1;
                            buffer.appendAttribute("colspan", colspan);
                        }

                        buffer.append(">");
                    } else {
                        buffer.append("<br/>");
                    }

                    // Write out field
                    field.render(buffer);
                    buffer.append("</td>\n");

                } else {
                    buffer.append("<td class=\"fields\"");

                    if (width != null) {
                        int colspan = (width.intValue() * 2);
                        buffer.appendAttribute("colspan", colspan);
                    } else {
                        buffer.appendAttribute("colspan", 2);
                    }
                    buffer.append(">\n");

                    control.render(buffer);

                    buffer.append("</td>\n");
                }

                if (width != null) {
                    if (control instanceof Label || !(control instanceof Field)) {
                        column += width.intValue();

                    } else {
                        column += (width.intValue() - 1);
                    }
                }

                if (column >= columns) {
                    buffer.append("</tr>\n");
                    openTableRow = false;
                    column = 1;

                } else {
                    column++;
                }
            }
        }

        if (openTableRow) {
            buffer.append("</tr>\n");
        }

        buffer.append("</tbody></table>\n");
    }

    /**
     * Render the form errors to the given buffer is form processed.
     *
     * @param buffer the string buffer to render the errors to
     * @param processed the flag indicating whether has been processed
     */
    protected void renderErrors(HtmlStringBuffer buffer, boolean processed) {

        if (processed && !isValid()) {

            buffer.append("<tr><td align=\"");
            buffer.append(getErrorsAlign());
            buffer.append("\">\n");
            buffer.append("<table class=\"errors\" id=\"");
            buffer.append(getId());
            buffer.append("-errors\"><tbody>\n");

            if (getError() != null) {
                buffer.append("<tr class=\"errors\">");
                buffer.append("<td class=\"errors\"");
                buffer.appendAttribute("align", getErrorsAlign());
                buffer.appendAttribute("colspan", getColumns() * 2);
                buffer.appendAttribute("style", getErrorsStyle());
                buffer.append(">\n");
                buffer.append("<span class=\"error\">");
                buffer.append(getError());
                buffer.append("</span>\n");
                buffer.append("</td></tr>\n");
            }

            for (Field field : getErrorFields()) {

                // Certain fields might be invalid because
                // one of their contained fields are invalid. However these
                // fields might not have an error message to display.
                // If the outer field's error message is null don't render.
                if (field.getError() == null) {
                    continue;
                }

                buffer.append("<tr class=\"errors\">");
                buffer.append("<td class=\"errors\"");
                buffer.appendAttribute("align", getErrorsAlign());
                buffer.appendAttribute("colspan", getColumns() * 2);
                buffer.appendAttribute("style", getErrorsStyle());
                buffer.append(">");

                buffer.append("<a class=\"error\"");
                buffer.append(" href=\"javascript:");
                buffer.append(field.getFocusJavaScript());
                buffer.append("\">");
                buffer.append(field.getError());
                buffer.append("</a>");
                buffer.append("</td></tr>\n");
            }

            buffer.append("</tbody></table>\n");
            buffer.append("</td></tr>\n");
        }

        // Render JavaScript form validation code
        if (isJavaScriptValidation()) {
            buffer.append("<tr style=\"display:none\" id=\"");
            buffer.append(getId());
            buffer.append("-errorsTr\"><td width='100%' align=\"");
            buffer.append(getErrorsAlign());
            buffer.append("\">\n");
            buffer.append("<div class=\"errors\" id=\"");
            buffer.append(getId());
            buffer.append("-errorsDiv\"></div>\n");
            buffer.append("</td></tr>\n");
        }
    }

    /**
     * Render the given list of Buttons to the string buffer.
     *
     * @param buffer the StringBuffer to render to
     */
    protected void renderButtons(HtmlStringBuffer buffer) {

        List<Button> buttons = getButtonList();

        if (!buttons.isEmpty()) {
            buffer.append("<tr><td");
            buffer.appendAttribute("align", getButtonAlign());
            buffer.append(">\n");

            buffer.append("<table class=\"buttons\" id=\"");
            buffer.append(getId());
            buffer.append("-buttons\"><tbody>\n");
            buffer.append("<tr class=\"buttons\">");

            for (Button button : buttons) {
                buffer.append("<td class=\"buttons\"");
                buffer.appendAttribute("style", getButtonStyle());
                buffer.closeTag();

                button.render(buffer);

                buffer.append("</td>");
            }

            buffer.append("</tr>\n");
            buffer.append("</tbody></table>\n");
            buffer.append("</td></tr>\n");
        }
    }

    /**
     * Close the form tag and render any additional content after the Form.
     * <p/>
     * Additional content includes <tt>javascript validation</tt> and
     * <tt>javascript focus</tt> scripts.
     *
     * @param formFields all fields contained within the form
     * @param buffer the buffer to render to
     */
    protected void renderTagEnd(List<Field> formFields, HtmlStringBuffer buffer) {

        buffer.elementEnd(getTag());
        buffer.append("\n");

        renderFocusJavaScript(buffer, formFields);

        renderValidationJavaScript(buffer, formFields);
    }

    /**
     * Render the Form field focus JavaScript to the string buffer.
     *
     * @param buffer the StringBuffer to render to
     * @param formFields the list of form fields
     */
    protected void renderFocusJavaScript(HtmlStringBuffer buffer, List<Field> formFields) {

        // Set field focus
        boolean errorFieldFound = false;
        for (int i = 0, size = formFields.size(); i < size; i++) {
            Field field = formFields.get(i);

            if (field.getError() != null && !field.isHidden() && !field.isDisabled()) {

                String focusJavaScript = StringUtils.replace(FOCUS_JAVASCRIPT, "$id", field.getId());
                buffer.append(focusJavaScript);
                errorFieldFound = true;
                break;
            }
        }

        if (!errorFieldFound) {
            for (int i = 0, size = formFields.size(); i < size; i++) {
                Field field = formFields.get(i);

                if (field.getFocus() && !field.isHidden() && !field.isDisabled()) {

                    String focusJavaScript = StringUtils.replace(FOCUS_JAVASCRIPT, "$id", field.getId());
                    buffer.append(focusJavaScript);
                    break;
                }
            }
        }
    }

    /**
     * Render the Form validation JavaScript to the string buffer.
     *
     * @param buffer the StringBuffer to render to
     * @param formFields the list of form fields
     */
    protected void renderValidationJavaScript(HtmlStringBuffer buffer, List<Field> formFields) {

        // Render JavaScript form validation code
        if (isJavaScriptValidation()) {
            List<String> functionNames = new ArrayList<String>();

            buffer.append("<script type=\"text/javascript\"><!--\n");

            // Render field validation functions & build list of function names
            for (Field field : formFields) {
                String fieldJS = field.getValidationJavaScript();
                if (fieldJS != null) {
                    buffer.append(fieldJS);

                    StringTokenizer tokenizer = new StringTokenizer(fieldJS);
                    tokenizer.nextToken();
                    functionNames.add(tokenizer.nextToken());
                }
            }

            if (!functionNames.isEmpty()) {
                buffer.append("function on_");
                buffer.append(getId());
                buffer.append("_submit() {\n");
                buffer.append("   var msgs = new Array(");
                buffer.append(functionNames.size());
                buffer.append(");\n");
                for (int i = 0; i < functionNames.size(); i++) {
                    buffer.append("   msgs[");
                    buffer.append(i);
                    buffer.append("] = ");
                    buffer.append(functionNames.get(i).toString());
                    buffer.append(";\n");
                }
                buffer.append("   return validateForm(msgs, '");
                buffer.append(getId());
                buffer.append("', '");
                buffer.append(getErrorsAlign());
                buffer.append("', ");
                if (getErrorsStyle() == null) {
                    buffer.append("null");
                } else {
                    buffer.append("'" + getErrorsStyle() + "'");
                }
                buffer.append(");\n");
                buffer.append("}\n");

            } else {
                buffer.append("function on_");
                buffer.append(getId());
                buffer.append("_submit() { return true; }\n");
            }
            buffer.append("//--></script>\n");
        }
    }

    /**
     * Returns true if a POST error occurred, false otherwise.
     *
     * @return true if a POST error occurred, false otherwise
     */
    protected boolean hasPostError() {
        Exception e = (Exception) getContext().getRequest().getAttribute(FileUploadService.UPLOAD_EXCEPTION);

        if (e instanceof FileSizeLimitExceededException || e instanceof SizeLimitExceededException) {
            return true;
        }

        return false;
    }

    /**
     * Validate the request for any file upload (multipart) errors.
     * <p/>
     * A form error message is displayed if a file upload error occurs.
     * These messages are defined in the resource bundle:
     * <blockquote>
     * <ul>
     *   <li>/click-control.properties
     *     <ul>
     *       <li>file-size-limit-exceeded-error</li>
     *       <li>post-size-limit-exceeded-error</li>
     *     </ul>
     *   </li>
     * </ul>
     * </blockquote>
     */
    protected void validateFileUpload() {
        setError(null);

        Exception exception = (Exception) getContext().getRequest()
                .getAttribute(FileUploadService.UPLOAD_EXCEPTION);

        if (!(exception instanceof FileUploadException)) {
            return;
        }

        FileUploadException fue = (FileUploadException) exception;

        String key = null;
        Object args[] = null;

        if (fue instanceof SizeLimitExceededException) {
            SizeLimitExceededException se = (SizeLimitExceededException) fue;

            key = "post-size-limit-exceeded-error";

            args = new Object[2];
            args[0] = se.getPermittedSize();
            args[1] = se.getActualSize();
            setError(getMessage(key, args));

        } else if (fue instanceof FileSizeLimitExceededException) {
            FileSizeLimitExceededException fse = (FileSizeLimitExceededException) fue;

            key = "file-size-limit-exceeded-error";

            // Parse the FileField name from the message
            String msg = fue.getMessage();
            int start = 10;
            int end = msg.indexOf(' ', start);
            String fieldName = fue.getMessage().substring(start, end);

            args = new Object[3];
            args[0] = ClickUtils.toLabel(fieldName);
            args[1] = fse.getPermittedSize();
            args[2] = fse.getActualSize();
            setError(getMessage(key, args));
        }
    }

    // Private Methods --------------------------------------------------------

    /**
    * Add fields for the given Container to the specified field list,
    * recursively including any Fields contained in child containers.
    *
    * @param container the container to obtain the fields from
    * @param fields the list of contained fields
    */
    private void addStatefulFields(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {
            if (control instanceof Label || control instanceof Button
                    || control instanceof NonProcessedHiddenField) {
                // Skip buttons and labels and NonProcessedHiddenFields
                continue;
            }

            if (control instanceof Field) {
                fields.add((Field) control);
            } else if (control instanceof Container) {
                Container childContainer = (Container) control;
                addStatefulFields(childContainer, fields);
            }
        }
    }

    /**
     * Return true if the control is hidden, false otherwise.
     *
     * @param control control to check hidden status
     * @return true if the control is hidden, false otherwise
     */
    private boolean isHidden(Control control) {
        if (!(control instanceof Field)) {
            // Non-Field Controls can not be hidden
            return false;
        } else {
            return ((Field) control).isHidden();
        }
    }

    // Inner Classes ----------------------------------------------------------

    /**
     * Provides a HiddenField which does not get processed or bind to its
     * incoming value. In addition the field name cannot be changed once set.
     */
    private static class NonProcessedHiddenField extends HiddenField {

        private static final long serialVersionUID = 1L;

        /**
         * Create a field with the given name and class.
         *
         * @param name the field name
         * @param valueClass the Class of the value Object
         */
        public NonProcessedHiddenField(String name, Class<?> valueClass) {
            super(name, valueClass);
        }

        /**
         * Create a field with the given name and value.
         *
         * @param name the field name
         * @param value the value of the field
         */
        public NonProcessedHiddenField(String name, Object value) {
            super(name, value);
        }

        /**
         * This method is overridden to not change the field name once it is set.
         *
         * @param name the name of the field
         */
        @Override
        public void setName(String name) {
            if (this.name != null) {
                return;
            }
            super.setName(name);
        }

        /**
         * Overridden to not process the field or bind to its request value.
         */
        @Override
        public boolean onProcess() {
            return true;
        }
    }

    /**
     * Provides a HiddenField which name and value cannot be changed, once it
     * is set.
     */
    private static class ImmutableHiddenField extends NonProcessedHiddenField {

        private static final long serialVersionUID = 1L;

        /**
          * Create a field with the given name and value.
          *
          * @param name the field name
          * @param value the value of the field
          */
        public ImmutableHiddenField(String name, Object value) {
            super(name, value);
        }

        /**
         * This method is overridden to not change the field value once it is set.
         *
         * @param value the field value
         */
        @Override
        public void setValue(String value) {
            if (this.value != null) {
                return;
            }
            super.setValue(value);
        }

        /**
         * This method is overridden to not change the field value object once
         * it is set.
         *
         * @param valueObject the field value object
         */
        @Override
        public void setValueObject(Object valueObject) {
            if (this.valueObject != null) {
                return;
            }
            super.setValueObject(valueObject);
        }
    }
}