| /* |
| * 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 '/> <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 -> form fields |
| * {@link #copyFrom(Object)}</li> |
| * <li>form fields -> value object |
| * {@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://mvel.codehaus.org/">MVEL</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"> |
| * <html> |
| * <head> |
| * <span class="blue">$headElements</span> |
| * </head> |
| * <body> |
| * |
| * <span class="red">$form</span> |
| * |
| * <span class="blue">$jsElements</span> |
| * </body> |
| * </html> </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: <tt>["left", "center", "right"]</tt></td> |
| * </tr><tr> |
| * <td>{@link #buttonStyle}</td> <td>button <td> "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: <tt>["left", "center", "right"]</tt></td> |
| * </tr><tr> |
| * <td>{@link #errorsPosition}</td> <td>validation error messages position: <tt>["top", "middle", "bottom"]</tt></td> |
| * </tr><tr> |
| * <td>{@link #errorsStyle}</td> <td>errors <td> "style" attribute value</td> |
| * </tr><tr> |
| * <td>{@link #fieldStyle}</td> <td>field <td> "style" attribute value</td> |
| * </tr><tr> |
| * <td>{@link #labelAlign}</td> <td>field label alignment: <tt>["left", "center", "right"]</tt></td> |
| * </tr><tr> |
| * <td>{@link #labelsPosition}</td> <td>label position relative to field: <tt>["left", "top"]</tt></td> |
| * </tr><tr> |
| * <td>{@link #labelStyle}</td> <td>label <td> "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> |
| * |
| * <table style="margin: 1em;"> |
| * |
| * <span class="red">#if</span> (<span class="blue">$form.error</span>) |
| * <tr> |
| * <td colspan="2" style="color: red;"> <span class="blue">$form.error</span> </td> |
| * </tr> |
| * <span class="red">#end</span> |
| * <span class="red">#if</span> (<span class="blue">$form.fields.usernameField.error</span>) |
| * <tr> |
| * <td colspan="2" style="color: red;"> <span class="blue">$form.fields.usernameField.error</span> </td> |
| * </tr> |
| * <span class="red">#end</span> |
| * <span class="red">#if</span> (<span class="blue">$form.fields.passwordField.error</span>) |
| * <tr> |
| * <td colspan="2" style="color: red;"> <span class="blue">$form.fields.passwordField.error</span> </td> |
| * </tr> |
| * <span class="red">#end</span> |
| * |
| * <tr> |
| * <td> Username: </td> |
| * <td> <span class="blue">$form.fields.usernameField</span> </td> |
| * </tr> |
| * <tr> |
| * <td> Password: </td> |
| * <td> <span class="blue">$form.fields.passwordField</span> </td> |
| * </tr> |
| * |
| * <tr> |
| * <td> |
| * <span class="blue">$form.fields.okSubmit</span> |
| * <span class="blue">$form.fields.cancelSubmit</span> |
| * </td> |
| * </tr> |
| * |
| * </table> |
| * |
| * <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> |
| * |
| * <table width="100%"> |
| * |
| * <span class="red">#if</span> (<span class="blue">$form.error</span>) |
| * <tr> |
| * <td colspan="2" style="color: red;"> <span class="blue">$form.error</span> </td> |
| * </tr> |
| * <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>) |
| * <tr> |
| * <td colspan="2"> <span class="blue">$field.error</span> </td> |
| * </tr> |
| * <span class="red">#end</span> |
| * |
| * <tr> |
| * <td> <span class="blue">$field.label</span>: </td><td> <span class="blue">$field</span> </td> |
| * </tr> |
| * <span class="red">#end</span> |
| * <span class="red">#end</span> |
| * |
| * <tr> |
| * <td colspan="2"> |
| * <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> &nbsp; |
| * <span class="red">#end</span> |
| * </td> |
| * </tr> |
| * |
| * </table> |
| * |
| * <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> <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: <tt>"left"</tt>. */ |
| public static final String ALIGN_LEFT = "left"; |
| |
| /** The align center, form layout constant: <tt>"center"</tt>. */ |
| public static final String ALIGN_CENTER = "center"; |
| |
| /** The align right, form layout constant: <tt>"right"</tt>. */ |
| public static final String ALIGN_RIGHT = "right"; |
| |
| /** |
| * The position top, errors and labels form layout constant: |
| * <tt>"top"</tt>. |
| */ |
| public static final String POSITION_TOP = "top"; |
| |
| /** |
| * The position middle, errors in middle form layout constant: |
| * <tt>"middle"</tt>. |
| */ |
| public static final String POSITION_MIDDLE = "middle"; |
| |
| /** |
| * The position bottom, errors on bottom form layout constant: |
| * <tt>"top"</tt>. |
| */ |
| public static final String POSITION_BOTTOM = "bottom"; |
| |
| /** |
| * The position left, labels of left form layout constant: |
| * <tt>"left"</tt>. |
| */ |
| public static final String POSITION_LEFT = "left"; |
| |
| /** |
| * The form name parameter for multiple forms: <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: |
| * <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: |
| * <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 <td> "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: <tt>"top"</tt>. |
| */ |
| protected String errorsPosition = POSITION_TOP; |
| |
| /** The error <td> "style" attribute value. */ |
| protected String errorsStyle; |
| |
| /** The field <td> "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: |
| * <tt>"left"</tt>. |
| */ |
| protected String labelsPosition = POSITION_LEFT; |
| |
| /** The label <td> "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 < 0 || index > 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 < 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 < 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 <td> 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 <td> 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 <td> "style" attribute value. |
| * |
| * @return the button <td> "style" attribute value |
| */ |
| public String getButtonStyle() { |
| return buttonStyle; |
| } |
| |
| /** |
| * Set the button <td> "style" attribute value. |
| * |
| * @param value the button <td> "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 <td> "style" attribute value. |
| * |
| * @return the error <td> "style" attribute value |
| */ |
| public String getErrorsStyle() { |
| return errorsStyle; |
| } |
| |
| /** |
| * Set the errors <td> "style" attribute value. |
| * |
| * @param value the errors <td> "style" attribute value |
| */ |
| public void setErrorsStyle(String value) { |
| this.errorsStyle = value; |
| } |
| |
| /** |
| * Return the field <td> "style" attribute value. |
| * |
| * @return the field <td> "style" attribute value |
| */ |
| public String getFieldStyle() { |
| return fieldStyle; |
| } |
| |
| /** |
| * Set the field <td> "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 <td> "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 <td> "style" attribute value. |
| * |
| * @return the label <td> "style" attribute value |
| */ |
| public String getLabelStyle() { |
| return labelStyle; |
| } |
| |
| /** |
| * Set the label <td> "style" attribute value. |
| * <p/> |
| * This value can be overridden by Fields through their |
| * {@link Field#setParentStyleHint(java.lang.String)} property. |
| * |
| * @param value the label <td> "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); |
| } |
| } |
| } |