0001: /*
0002: * $Id: Form.java 516860 2007-03-11 06:12:05Z ehillenius $
0003: * $Revision: 516860 $ $Date: 2007-03-11 07:12:05 +0100 (Sun, 11 Mar 2007) $
0004: *
0005: * ==============================================================================
0006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0007: * use this file except in compliance with the License. You may obtain a copy of
0008: * the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing, software
0013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0015: * License for the specific language governing permissions and limitations under
0016: * the License.
0017: */
0018: package wicket.markup.html.form;
0019:
0020: import java.util.HashMap;
0021: import java.util.Iterator;
0022: import java.util.Locale;
0023: import java.util.Map;
0024: import java.util.StringTokenizer;
0025:
0026: import org.apache.commons.logging.Log;
0027: import org.apache.commons.logging.LogFactory;
0028:
0029: import wicket.Component;
0030: import wicket.IRequestTarget;
0031: import wicket.MarkupContainer;
0032: import wicket.Page;
0033: import wicket.Request;
0034: import wicket.RequestCycle;
0035: import wicket.WicketRuntimeException;
0036: import wicket.markup.ComponentTag;
0037: import wicket.markup.MarkupStream;
0038: import wicket.markup.html.WebMarkupContainer;
0039: import wicket.markup.html.border.Border;
0040: import wicket.markup.html.form.persistence.CookieValuePersister;
0041: import wicket.markup.html.form.persistence.IValuePersister;
0042: import wicket.markup.html.form.validation.IFormValidator;
0043: import wicket.model.IModel;
0044: import wicket.model.Model;
0045: import wicket.protocol.http.WebRequest;
0046: import wicket.protocol.http.WebRequestCycle;
0047: import wicket.protocol.http.request.WebClientInfo;
0048: import wicket.request.IRequestCycleProcessor;
0049: import wicket.request.RequestParameters;
0050: import wicket.request.target.component.listener.ListenerInterfaceRequestTarget;
0051: import wicket.util.lang.Bytes;
0052: import wicket.util.string.AppendingStringBuffer;
0053: import wicket.util.string.Strings;
0054: import wicket.util.string.interpolator.MapVariableInterpolator;
0055: import wicket.util.upload.FileUploadException;
0056: import wicket.util.upload.FileUploadBase.SizeLimitExceededException;
0057:
0058: /**
0059: * Base class for forms. To implement a form, subclass this class, add
0060: * FormComponents (such as CheckBoxes, ListChoices or TextFields) to the form.
0061: * You can nest multiple buttons if you want to vary submit behavior. However,
0062: * it is not necessary to use Wicket's button class, just putting e.g. <input
0063: * type="submit" value="go"> suffices.
0064: * <p>
0065: * By default, the processing of a form works like this:
0066: * <li> The submitting button is looked up. A submitting button is a button that
0067: * is nested in this form (is a child component) and that was clicked by the
0068: * user. If a submitting button was found, and it has the defaultFormProcessing
0069: * field set to false (default is true), it's onSubmit method will be called
0070: * right away, thus no validition is done, and things like updating form
0071: * component models that would normally be done are skipped. In that respect,
0072: * nesting a button with the defaultFormProcessing field set to false has the
0073: * same effect as nesting a normal link. If you want you can call validate() to
0074: * execute form validation, hasError() to find out whether validate() resulted
0075: * in validation errors, and updateFormComponentModels() to update the models of
0076: * nested form components. </li>
0077: * <li> When no submitting button with defaultFormProcessing set to false was
0078: * found, this form is processed (method process()). Now, two possible paths
0079: * exist:
0080: * <ul>
0081: * <li> Form validation failed. All nested form components will be marked
0082: * invalid, and onError() is called to allow clients to provide custom error
0083: * handling code. </li>
0084: * <li> Form validation succeeded. The nested components will be asked to update
0085: * their models and persist their data is applicable. After that, method
0086: * delegateSubmit with optionally the submitting button is called. The default
0087: * when there is a submitting button is to first call onSubmit on that button,
0088: * and after that call onSubmit on this form. Clients may override
0089: * delegateSubmit if they want different behavior. </li>
0090: * </ul>
0091: * </li>
0092: * </li>
0093: * </p>
0094: *
0095: * Form for handling (file) uploads with multipart requests is supported by
0096: * callign setMultiPart(true) ( although wicket will try to automatically detect
0097: * this for you ). Use this with
0098: * {@link wicket.markup.html.form.upload.FileUploadField} components. You can
0099: * attach mutliple FileUploadField components for muliple file uploads.
0100: * <p>
0101: * In case of an upload error two resource keys are available to specify error
0102: * messages: uploadTooLarge and uploadFailed
0103: *
0104: * ie in [page].properties
0105: *
0106: * [form-id].uploadTooLarge=You have uploaded a file that is over the allowed
0107: * limit of 2Mb
0108: *
0109: * <p>
0110: * If you want to have multiple buttons which submit the same form, simply put
0111: * two or more button components somewhere in the hierarchy of components that
0112: * are children of the form.
0113: * </p>
0114: * <p>
0115: * To get form components to persist their values for users via cookies, simply
0116: * call setPersistent(true) on the form component.
0117: * </p>
0118: *
0119: * @author Jonathan Locke
0120: * @author Juergen Donnerstag
0121: * @author Eelco Hillenius
0122: * @author Cameron Braid
0123: * @author Johan Compagner
0124: * @author Igor Vaynberg (ivaynberg)
0125: */
0126: public class Form extends WebMarkupContainer implements
0127: IFormSubmitListener {
0128: /**
0129: * Visitor used for validation
0130: *
0131: * @author Igor Vaynberg (ivaynberg)
0132: */
0133: private static abstract class ValidationVisitor implements
0134: FormComponent.IVisitor {
0135:
0136: /**
0137: * @see wicket.markup.html.form.FormComponent.IVisitor#formComponent(wicket.markup.html.form.FormComponent)
0138: */
0139: public void formComponent(FormComponent formComponent) {
0140: if (formComponent.isVisibleInHierarchy()
0141: && formComponent.isValid()
0142: && formComponent.isEnabled()
0143: && formComponent.isEnableAllowed()) {
0144: validate(formComponent);
0145: }
0146: }
0147:
0148: /**
0149: * Callback that should be used to validate form component
0150: *
0151: * @param formComponent
0152: */
0153: public abstract void validate(FormComponent formComponent);
0154:
0155: }
0156:
0157: private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge";
0158:
0159: private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed";
0160:
0161: /** Flag that indicates this form has been submitted during this request */
0162: private static final short FLAG_SUBMITTED = FLAG_RESERVED1;
0163:
0164: private static final long serialVersionUID = 1L;
0165:
0166: /** Log. */
0167: private static final Log log = LogFactory.getLog(Form.class);
0168:
0169: /** Maximum size of an upload in bytes */
0170: private Bytes maxSize = Bytes.MAX;
0171:
0172: /** True if the form has enctype of multipart/form-data */
0173: private boolean multiPart = false;
0174:
0175: private String javascriptId;
0176:
0177: /** multi-validators assigned to this form */
0178: private Object formValidators = null;
0179:
0180: /**
0181: * Any default button. If set, a hidden submit button will be rendered right
0182: * after the form tag, so that when users press enter in a textfield, this
0183: * button's action will be selected. If no default button is set, nothing
0184: * additional is rendered.
0185: * <p>
0186: * WARNING: note that this is a best effort only. Unfortunately having a
0187: * 'default' button in a form is ill defined in the standards, and of course
0188: * IE has it's own way of doing things.
0189: * </p>
0190: */
0191: private Button defaultButton;
0192:
0193: /**
0194: * Constructs a form with no validation.
0195: *
0196: * @param id
0197: * See Component
0198: */
0199: public Form(final String id) {
0200: super (id);
0201: }
0202:
0203: /**
0204: * @param id
0205: * See Component
0206: * @param model
0207: * See Component
0208: * @see wicket.Component#Component(String, IModel)
0209: */
0210: public Form(final String id, IModel model) {
0211: super (id, model);
0212: }
0213:
0214: /**
0215: * Gets the default button. If set (not null), a hidden submit button will
0216: * be rendered right after the form tag, so that when users press enter in a
0217: * textfield, this button's action will be selected. If no default button is
0218: * set (it is null), nothing additional is rendered.
0219: * <p>
0220: * WARNING: note that this is a best effort only. Unfortunately having a
0221: * 'default' button in a form is ill defined in the standards, and of course
0222: * IE has it's own way of doing things.
0223: * </p>
0224: *
0225: * @return The button to set as the default button, or null when you want to
0226: * 'unset' any previously set default button
0227: */
0228: public final Button getDefaultButton() {
0229: return defaultButton;
0230: }
0231:
0232: /**
0233: * @return the maxSize of uploaded files
0234: */
0235: public Bytes getMaxSize() {
0236: return this .maxSize;
0237: }
0238:
0239: /**
0240: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0241: * <p>
0242: * Retrieves FormComponent values related to the page using the persister
0243: * and assign the values to the FormComponent. Thus initializing them.
0244: */
0245: public final void loadPersistentFormComponentValues() {
0246: visitFormComponents(new FormComponent.IVisitor() {
0247: public void formComponent(final FormComponent formComponent) {
0248: // Component must implement persister interface and
0249: // persistence for that component must be enabled.
0250: // Else ignore the persisted value. It'll be deleted
0251: // once the user submits the Form containing that FormComponent.
0252: // Note: if that is true, values may remain persisted longer
0253: // than really necessary
0254: if (formComponent.isVisibleInHierarchy()
0255: && formComponent.isPersistent()) {
0256: // The persister
0257: final IValuePersister persister = getValuePersister();
0258:
0259: // Retrieve persisted value
0260: persister.load(formComponent);
0261: }
0262: }
0263: });
0264: }
0265:
0266: /**
0267: * THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR
0268: * CALL IT.
0269: *
0270: * Handles form submissions.
0271: *
0272: * @see Form#validate()
0273: */
0274: public final void onFormSubmitted() {
0275: setFlag(FLAG_SUBMITTED, true);
0276:
0277: if (handleMultiPart()) {
0278: // Tells FormComponents that a new user input has come
0279: inputChanged();
0280:
0281: String url = getRequest().getParameter(getHiddenFieldId());
0282: if (!Strings.isEmpty(url)) {
0283: dispatchEvent(getPage(), url);
0284: } else {
0285: // First, see if the processing was triggered by a Wicket button
0286: final Button submittingButton = findSubmittingButton();
0287:
0288: // When processing was triggered by a Wicket button and that
0289: // button indicates it wants to be called immediately
0290: // (without processing), call Button.onSubmit() right away.
0291: if (submittingButton != null
0292: && !submittingButton.getDefaultFormProcessing()) {
0293: submittingButton.onSubmit();
0294: } else {
0295: // process the form for this request
0296: if (process()) {
0297: // let clients handle further processing
0298: delegateSubmit(submittingButton);
0299: }
0300: }
0301: }
0302: }
0303: // If multi part did fail check if an error is registered and call
0304: // onError
0305: else if (hasError()) {
0306: onError();
0307: }
0308: }
0309:
0310: /**
0311: * Checks if this form has been submitted during the current request
0312: *
0313: * @return true if the form has been submitted during this request, false
0314: * otherwise
0315: */
0316: public final boolean isSubmitted() {
0317: return getFlag(FLAG_SUBMITTED);
0318: }
0319:
0320: /**
0321: * @see wicket.Component#internalOnDetach()
0322: */
0323: protected void internalOnDetach() {
0324: super .internalOnDetach();
0325: setFlag(FLAG_SUBMITTED, false);
0326: }
0327:
0328: /**
0329: * Removes already persisted data for all FormComponent childs and disable
0330: * persistence for the same components.
0331: *
0332: * @see Page#removePersistedFormData(Class, boolean)
0333: *
0334: * @param disablePersistence
0335: * if true, disable persistence for all FormComponents on that
0336: * page. If false, it will remain unchanged.
0337: */
0338: public void removePersistentFormComponentValues(
0339: final boolean disablePersistence) {
0340: // The persistence manager responsible to persist and retrieve
0341: // FormComponent data
0342: final IValuePersister persister = getValuePersister();
0343:
0344: // Search for FormComponents like TextField etc.
0345: visitFormComponents(new FormComponent.IVisitor() {
0346: public void formComponent(final FormComponent formComponent) {
0347: if (formComponent.isVisibleInHierarchy()) {
0348: // remove the FormComponent's persisted data
0349: persister.clear(formComponent);
0350:
0351: // Disable persistence if requested. Leave unchanged
0352: // otherwise.
0353: if (formComponent.isPersistent()
0354: && disablePersistence) {
0355: formComponent.setPersistent(false);
0356: }
0357: }
0358: }
0359: });
0360: }
0361:
0362: /**
0363: * Sets the default button. If set (not null), a hidden submit button will
0364: * be rendered right after the form tag, so that when users press enter in a
0365: * textfield, this button's action will be selected. If no default button is
0366: * set (so unset by calling this method with null), nothing additional is
0367: * rendered.
0368: * <p>
0369: * WARNING: note that this is a best effort only. Unfortunately having a
0370: * 'default' button in a form is ill defined in the standards, and of course
0371: * IE has it's own way of doing things.
0372: * </p>
0373: *
0374: * @param button
0375: * The button to set as the default button, or null when you want
0376: * to 'unset' any previously set default button
0377: */
0378: public final void setDefaultButton(Button button) {
0379: this .defaultButton = button;
0380: }
0381:
0382: /**
0383: * @param maxSize
0384: * The maxSize for uploaded files
0385: */
0386: public void setMaxSize(final Bytes maxSize) {
0387: this .maxSize = maxSize;
0388: }
0389:
0390: /**
0391: * Set to true to use enctype='multipart/form-data', and to process file
0392: * uplloads by default multiPart = false
0393: *
0394: * @param multiPart
0395: * whether this form should behave as a multipart form
0396: */
0397: public void setMultiPart(boolean multiPart) {
0398: this .multiPart = multiPart;
0399: }
0400:
0401: /**
0402: * @see wicket.Component#setVersioned(boolean)
0403: */
0404: public final Component setVersioned(final boolean isVersioned) {
0405: super .setVersioned(isVersioned);
0406:
0407: // Search for FormComponents like TextField etc.
0408: visitFormComponents(new FormComponent.IVisitor() {
0409: public void formComponent(final FormComponent formComponent) {
0410: formComponent.setVersioned(isVersioned);
0411: }
0412: });
0413: return this ;
0414: }
0415:
0416: /**
0417: * Method made final because we want to ensure users call setVersioned.
0418: *
0419: * @see wicket.Component#isVersioned()
0420: */
0421: public boolean isVersioned() {
0422: return super .isVersioned();
0423: }
0424:
0425: /**
0426: * Convenient and typesafe way to visit all the form components on a form
0427: *
0428: * @param visitor
0429: * The visitor interface to call
0430: */
0431: public final void visitFormComponents(
0432: final FormComponent.IVisitor visitor) {
0433: visitChildren(FormComponent.class, new IVisitor() {
0434: public Object component(final Component component) {
0435: visitor.formComponent((FormComponent) component);
0436: return CONTINUE_TRAVERSAL;
0437: }
0438: });
0439:
0440: /**
0441: * TODO Post 1.2 General: Maybe we should re-think how Borders are
0442: * implemented, because there are just too many exceptions in the code
0443: * base because of borders. This time it is to solve the problem tested
0444: * in BoxBorderTestPage_3 where the Form is defined in the box border
0445: * and the FormComponents are in the "body". Thus, the formComponents
0446: * are not childs of the form. They are rather childs of the border, as
0447: * the Form itself.
0448: */
0449: if (getParent() instanceof Border) {
0450: MarkupContainer border = getParent();
0451: Iterator iter = border.iterator();
0452: while (iter.hasNext()) {
0453: Component child = (Component) iter.next();
0454: if (child instanceof FormComponent) {
0455: visitor.formComponent((FormComponent) child);
0456: }
0457: }
0458: }
0459: }
0460:
0461: /**
0462: * If a default button was set on this form, this method will be called to
0463: * render an extra field with an invisible style so that pressing enter in
0464: * one of the textfields will do a form submit using this button. This
0465: * method is overridable as what we do is best effort only, and may not what
0466: * you want in specific situations. So if you have specific usability
0467: * concerns, or want to follow another strategy, you may override this
0468: * method.
0469: *
0470: * @param markupStream
0471: * The markup stream
0472: * @param openTag
0473: * The open tag for the body
0474: */
0475: protected void appendDefaultButtonField(
0476: final MarkupStream markupStream, final ComponentTag openTag) {
0477: String nameAndId = getHiddenFieldId();
0478: AppendingStringBuffer buffer = new AppendingStringBuffer();
0479: // get the value, first seeing whether the value attribute is set
0480: // by a model
0481: String value = defaultButton.getModelObjectAsString();
0482: if (value == null || "".equals(value)) {
0483: // nope it isn't; try to read from the attributes
0484: // note that we're only trying lower case here
0485: value = defaultButton.getMarkupAttributes().getString(
0486: "value");
0487: }
0488:
0489: // append the button
0490: String userAgent = ((WebClientInfo) getSession()
0491: .getClientInfo()).getUserAgent();
0492: buffer.append("<input type=\"submit\" value=\"").append(value)
0493: .append("\" name=\"").append(
0494: defaultButton.getInputName()).append("\"");
0495: if (userAgent != null && userAgent.indexOf("MSIE") != -1) {
0496: buffer
0497: .append("style=\"width: 0px; height: 0px; position: absolute;\"");
0498: } else {
0499: buffer.append(" style=\"display: none\"");
0500: }
0501: buffer.append(" />");
0502: getResponse().write(buffer);
0503: }
0504:
0505: /**
0506: * Template method to allow clients to do any processing (like recording the
0507: * current model so that, in case onSubmit does further validation, the
0508: * model can be rolled back) before the actual updating of form component
0509: * models is done.
0510: */
0511: protected void beforeUpdateFormComponentModels() {
0512: }
0513:
0514: /**
0515: * Called (by the default implementation of 'process') when all fields
0516: * validated, the form was updated and it's data was allowed to be
0517: * persisted. It is meant for delegating further processing to clients.
0518: * <p>
0519: * This implementation first finds out whether the form processing was
0520: * triggered by a nested button of this form. If that is the case, that
0521: * button's onSubmit is called first.
0522: * </p>
0523: * <p>
0524: * Regardless of whether a submitting button was found, the form's onSubmit
0525: * method is called next.
0526: * </p>
0527: *
0528: * @param submittingButton
0529: * the button that triggered this form processing, or null if the
0530: * processing was triggered by something else (like a non-Wicket
0531: * submit button or a javascript execution)
0532: */
0533: protected void delegateSubmit(Button submittingButton) {
0534: // when the given button is not null, it means that it was the
0535: // submitting button
0536: if (submittingButton != null) {
0537: submittingButton.onSubmit();
0538: }
0539:
0540: // Model was successfully updated with valid data
0541: onSubmit();
0542: }
0543:
0544: /**
0545: * Gets the button which submitted this form.
0546: *
0547: * @return The button which submitted this form or null if the processing
0548: * was not trigger by a registered button component
0549: */
0550: public final Button findSubmittingButton() {
0551: Button button = (Button) visitChildren(Button.class,
0552: new IVisitor() {
0553: public Object component(final Component component) {
0554: // Get button
0555: final Button button = (Button) component;
0556:
0557: // Check for button-name or button-name.x request string
0558: if (getRequest().getParameter(
0559: button.getInputName()) != null
0560: || getRequest().getParameter(
0561: button.getInputName() + ".x") != null) {
0562: if (!button.isVisible()) {
0563: throw new WicketRuntimeException(
0564: "Submit Button "
0565: + button.getInputName()
0566: + " (path="
0567: + button
0568: .getPageRelativePath()
0569: + ") is not visible");
0570: }
0571: return button;
0572: }
0573: return CONTINUE_TRAVERSAL;
0574: }
0575: });
0576:
0577: if (button == null) {
0578: button = (Button) getPage().visitChildren(SubmitLink.class,
0579: new IVisitor() {
0580: public Object component(
0581: final Component component) {
0582: // Get button
0583: final SubmitLink button = (SubmitLink) component;
0584:
0585: // Check for button-name or button-name.x request string
0586: if (button.getForm() == Form.this
0587: && (getRequest().getParameter(
0588: button.getInputName()) != null || getRequest()
0589: .getParameter(
0590: button
0591: .getInputName()
0592: + ".x") != null)) {
0593: if (!button.isVisible()) {
0594: throw new WicketRuntimeException(
0595: "Submit Button is not visible");
0596: }
0597: return button;
0598: }
0599: return CONTINUE_TRAVERSAL;
0600: }
0601: });
0602: }
0603: return button;
0604: }
0605:
0606: /**
0607: * Gets the form component persistence manager; it is lazy loaded.
0608: *
0609: * @return The form component value persister
0610: */
0611: protected IValuePersister getValuePersister() {
0612: return new CookieValuePersister();
0613: }
0614:
0615: /**
0616: * Gets whether the current form has any error registered.
0617: *
0618: * @return True if this form has at least one error.
0619: */
0620: public final boolean hasError() {
0621: // if this form itself has an error message
0622: if (hasErrorMessage()) {
0623: return true;
0624: }
0625:
0626: // the form doesn't have any errors, now check any nested form
0627: // components
0628: return anyFormComponentError();
0629: }
0630:
0631: /**
0632: * @see wicket.Component#internalOnModelChanged()
0633: */
0634: protected void internalOnModelChanged() {
0635: // Visit all the form components and validate each
0636: visitFormComponents(new FormComponent.IVisitor() {
0637: public void formComponent(final FormComponent formComponent) {
0638: // If form component is using form model
0639: if (formComponent.sameRootModel(Form.this )) {
0640: formComponent.modelChanged();
0641: }
0642: }
0643: });
0644: }
0645:
0646: /**
0647: * Mark each form component on this form invalid.
0648: */
0649: protected final void markFormComponentsInvalid() {
0650: // call invalidate methods of all nested form components
0651: visitFormComponents(new FormComponent.IVisitor() {
0652: public void formComponent(final FormComponent formComponent) {
0653: if (formComponent.isVisibleInHierarchy()) {
0654: formComponent.invalid();
0655: }
0656: }
0657: });
0658: }
0659:
0660: /**
0661: * Mark each form component on this form valid.
0662: */
0663: protected final void markFormComponentsValid() {
0664: // call invalidate methods of all nested form components
0665: visitFormComponents(new FormComponent.IVisitor() {
0666: public void formComponent(final FormComponent formComponent) {
0667: if (formComponent.isVisibleInHierarchy()) {
0668: formComponent.valid();
0669: }
0670: }
0671: });
0672: }
0673:
0674: /**
0675: * Returns the HiddenFieldId which will be used as the name and id property
0676: * of the hiddenfield that is generated for event dispatches.
0677: *
0678: * @return The name and id of the hidden field.
0679: */
0680: protected final String getHiddenFieldId() {
0681: return getJavascriptId() + ":hf:0";
0682: }
0683:
0684: /**
0685: * Returns the javascript/css id of this form that will be used to generated
0686: * the id="xxx" attribute. it will be generated if not set already in the
0687: * onComponentTag. Where it will be tried to load from the markup first
0688: * before it is generated.
0689: *
0690: * @return The javascript/css id of this form.
0691: */
0692: protected final String getJavascriptId() {
0693: if (Strings.isEmpty(javascriptId)) {
0694: javascriptId = getMarkupId();
0695: }
0696: return javascriptId;
0697: }
0698:
0699: /**
0700: * Append an additional hidden input tag to support anchor tags that can
0701: * submit a form.
0702: *
0703: * @param markupStream
0704: * The markup stream
0705: * @param openTag
0706: * The open tag for the body
0707: */
0708: protected void onComponentTagBody(final MarkupStream markupStream,
0709: final ComponentTag openTag) {
0710: // get the hidden field id
0711: String nameAndId = getHiddenFieldId();
0712:
0713: // render the hidden field
0714: AppendingStringBuffer buffer = new AppendingStringBuffer(
0715: "<div style=\"display:none\"><input type=\"hidden\" name=\"")
0716: .append(nameAndId).append("\" id=\"").append(nameAndId)
0717: .append("\" /></div>");
0718: getResponse().write(buffer);
0719:
0720: // if a default button was set, handle the rendering of that
0721: if (defaultButton != null
0722: && defaultButton.isVisibleInHierarchy()
0723: && defaultButton.isEnabled()) {
0724: appendDefaultButtonField(markupStream, openTag);
0725: }
0726:
0727: // do the rest of the processing
0728: super .onComponentTagBody(markupStream, openTag);
0729: }
0730:
0731: /**
0732: * @see wicket.Component#onComponentTag(ComponentTag)
0733: */
0734: protected void onComponentTag(final ComponentTag tag) {
0735: checkComponentTag(tag, "form");
0736: super .onComponentTag(tag);
0737:
0738: // If the javascriptid is already generated then use that on even it was
0739: // before the first render. Bbecause there could be a component which
0740: // already uses it to submit the forum. This should be fixed when we
0741: // pre parse the markup so that we know the id is at front.
0742: if (!Strings.isEmpty(javascriptId)) {
0743: tag.put("id", javascriptId);
0744: } else {
0745: javascriptId = (String) tag.getAttributes().get("id");
0746: if (Strings.isEmpty(javascriptId)) {
0747: javascriptId = getJavascriptId();
0748: tag.put("id", javascriptId);
0749: }
0750: }
0751: tag.put("method", "post");
0752: tag.put("action", Strings.replaceAll(
0753: urlFor(IFormSubmitListener.INTERFACE), "&", "&"));
0754: if (multiPart) {
0755: tag.put("enctype", "multipart/form-data");
0756: } else {
0757: // sanity check
0758: String enctype = (String) tag.getAttributes()
0759: .get("enctype");
0760: if ("multipart/form-data".equalsIgnoreCase(enctype)) {
0761: // though not set explicitly in Java, this is a multipart form
0762: setMultiPart(true);
0763: }
0764: }
0765: }
0766:
0767: /**
0768: * Method to override if you want to do something special when an error
0769: * occurs (other than simply displaying validation errors).
0770: */
0771: protected void onError() {
0772: }
0773:
0774: /**
0775: * @see wicket.Component#onRender(MarkupStream)
0776: */
0777: protected void onRender(final MarkupStream markupStream) {
0778: // Force multi-part on if any child form component is multi-part
0779: visitFormComponents(new FormComponent.IVisitor() {
0780: public void formComponent(FormComponent formComponent) {
0781: if (formComponent.isVisible()
0782: && formComponent.isMultiPart()) {
0783: setMultiPart(true);
0784: }
0785: }
0786: });
0787:
0788: super .onRender(markupStream);
0789: }
0790:
0791: /**
0792: * Implemented by subclasses to deal with form submits.
0793: */
0794: protected void onSubmit() {
0795: }
0796:
0797: /**
0798: * Process the form. Though you can override this method to provide your
0799: * whole own algorithm, it is not recommended to do so.
0800: * <p>
0801: * See the class documentation for further details on the form processing
0802: * </p>
0803: *
0804: * @return False if the form had an error
0805: */
0806: public boolean process() {
0807: // run validation
0808: validate();
0809:
0810: // If a validation error occurred
0811: if (hasError()) {
0812: // mark all children as invalid
0813: markFormComponentsInvalid();
0814:
0815: // let subclass handle error
0816: onError();
0817:
0818: // Form has an error
0819: return false;
0820: } else {
0821: // mark all childeren as valid
0822: markFormComponentsValid();
0823:
0824: // before updating, call the interception method for clients
0825: beforeUpdateFormComponentModels();
0826:
0827: // Update model using form data
0828: updateFormComponentModels();
0829:
0830: // Persist FormComponents if requested
0831: persistFormComponentData();
0832:
0833: // Form has no error
0834: return true;
0835: }
0836: }
0837:
0838: /**
0839: * Update the model of all form components using the fields that were sent
0840: * with the current request.
0841: *
0842: * @see wicket.markup.html.form.FormComponent#updateModel()
0843: */
0844: protected final void updateFormComponentModels() {
0845: visitFormComponents(new FormComponent.IVisitor() {
0846: public void formComponent(final FormComponent formComponent) {
0847: // Only update the component when it is visible and valid
0848: if (formComponent.isVisibleInHierarchy()
0849: && formComponent.isEnabled()
0850: && formComponent.isValid()
0851: && formComponent.isEnableAllowed()) {
0852: // Potentially update the model
0853: formComponent.updateModel();
0854: }
0855: }
0856: });
0857: }
0858:
0859: /**
0860: * Clears the input from the form's nested children of type
0861: * {@link FormComponent}. This method is typically called when a form needs
0862: * to be reset.
0863: */
0864: public final void clearInput() {
0865: // Visit all the (visible) form components and clear the input on each.
0866: visitFormComponents(new FormComponent.IVisitor() {
0867: public void formComponent(final FormComponent formComponent) {
0868: if (formComponent.isVisibleInHierarchy()) {
0869: // Clear input from form component
0870: formComponent.clearInput();
0871: }
0872: }
0873: });
0874: }
0875:
0876: /**
0877: * Validates the form. This method is typically called before updating any
0878: * models.
0879: */
0880: protected void validate() {
0881: validateRequired();
0882:
0883: validateConversion();
0884:
0885: validateValidators();
0886:
0887: validateFormValidators();
0888: }
0889:
0890: /**
0891: * Triggers input required attribute validation on all form components
0892: */
0893: private void validateRequired() {
0894: visitFormComponents(new ValidationVisitor() {
0895: public void validate(final FormComponent formComponent) {
0896: formComponent.validateRequired();
0897: }
0898: });
0899: }
0900:
0901: /**
0902: * Triggers type conversion on form components
0903: */
0904: private void validateConversion() {
0905: visitFormComponents(new ValidationVisitor() {
0906: public void validate(final FormComponent formComponent) {
0907: formComponent.convert();
0908: }
0909: });
0910: }
0911:
0912: /**
0913: * Triggers all IValidator validators added to the form components
0914: */
0915: private void validateValidators() {
0916: visitFormComponents(new ValidationVisitor() {
0917: public void validate(final FormComponent formComponent) {
0918: formComponent.validateValidators();
0919: }
0920: });
0921: }
0922:
0923: /**
0924: * Triggers any added {@link IFormValidator}s.
0925: */
0926: private void validateFormValidators() {
0927: final int count = formValidators_size();
0928: for (int i = 0; i < count; i++) {
0929: validateFormValidator(formValidators_get(i));
0930: }
0931: }
0932:
0933: /**
0934: * Validates form with the given form validator
0935: *
0936: * @param validator
0937: */
0938: protected final void validateFormValidator(
0939: final IFormValidator validator) {
0940: if (validator == null) {
0941: throw new IllegalArgumentException(
0942: "Argument [[validator]] cannot be null");
0943: }
0944:
0945: final FormComponent[] dependents = validator
0946: .getDependentFormComponents();
0947:
0948: boolean validate = true;
0949:
0950: if (dependents != null) {
0951: for (int j = 0; j < dependents.length; j++) {
0952: final FormComponent dependent = dependents[j];
0953: if (!dependent.isValid()) {
0954: validate = false;
0955: break;
0956: }
0957: }
0958: }
0959:
0960: if (validate) {
0961: validator.validate(this );
0962: }
0963: }
0964:
0965: /**
0966: * Find out whether there is any registered error for a form component.
0967: *
0968: * @return whether there is any registered error for a form component
0969: */
0970: private boolean anyFormComponentError() {
0971: final Object value = visitChildren(new IVisitor() {
0972: public Object component(final Component component) {
0973: if (component.hasErrorMessage()) {
0974: return STOP_TRAVERSAL;
0975: }
0976:
0977: // Traverse all children
0978: return CONTINUE_TRAVERSAL;
0979: }
0980: });
0981:
0982: return value == IVisitor.STOP_TRAVERSAL ? true : false;
0983: }
0984:
0985: /**
0986: * @return False if form is multipart and upload failed
0987: */
0988: private final boolean handleMultiPart() {
0989: if (multiPart) {
0990: // Change the request to a multipart web request so parameters are
0991: // parsed out correctly
0992: try {
0993: final WebRequest multipartWebRequest = ((WebRequest) getRequest())
0994: .newMultipartWebRequest(this .maxSize);
0995: getRequestCycle().setRequest(multipartWebRequest);
0996: } catch (WicketRuntimeException wre) {
0997: if (wre.getCause() == null
0998: || !(wre.getCause() instanceof FileUploadException)) {
0999: throw wre;
1000: }
1001:
1002: FileUploadException e = (FileUploadException) wre
1003: .getCause();
1004: // Create model with exception and maximum size values
1005: final Map model = new HashMap();
1006: model.put("exception", e);
1007: model.put("maxSize", maxSize);
1008:
1009: if (e instanceof SizeLimitExceededException) {
1010: // Resource key should be <form-id>.uploadTooLarge to
1011: // override default message
1012: final String defaultValue = "Upload must be less than "
1013: + maxSize;
1014: String msg = getString(getId() + "."
1015: + UPLOAD_TOO_LARGE_RESOURCE_KEY, Model
1016: .valueOf(model), defaultValue);
1017: error(msg);
1018:
1019: if (log.isDebugEnabled()) {
1020: log.error(msg, e);
1021: } else {
1022: log.error(msg);
1023: }
1024: } else {
1025: // Resource key should be <form-id>.uploadFailed to override
1026: // default message
1027: final String defaultValue = "Upload failed: "
1028: + e.getLocalizedMessage();
1029: String msg = getString(getId() + "."
1030: + UPLOAD_FAILED_RESOURCE_KEY, Model
1031: .valueOf(model), defaultValue);
1032: error(msg);
1033:
1034: log.error(msg, e);
1035: }
1036:
1037: // don't process the form if there is a FileUploadException
1038: return false;
1039: }
1040: }
1041: return true;
1042: }
1043:
1044: /**
1045: * Persist (e.g. Cookie) FormComponent data to be reloaded and re-assigned
1046: * to the FormComponent automatically when the page is visited by the user
1047: * next time.
1048: *
1049: * @see wicket.markup.html.form.FormComponent#updateModel()
1050: */
1051: private void persistFormComponentData() {
1052: // Cannot add cookies to request cycle unless it accepts them
1053: // We could conceivably be HTML over some other protocol!
1054: if (getRequestCycle() instanceof WebRequestCycle) {
1055: // The persistence manager responsible to persist and retrieve
1056: // FormComponent data
1057: final IValuePersister persister = getValuePersister();
1058:
1059: // Search for FormComponent children. Ignore all other
1060: visitFormComponents(new FormComponent.IVisitor() {
1061: public void formComponent(
1062: final FormComponent formComponent) {
1063: if (formComponent.isVisibleInHierarchy()) {
1064: // If peristence is switched on for that FormComponent
1065: // ...
1066: if (formComponent.isPersistent()) {
1067: // Save component's data (e.g. in a cookie)
1068: persister.save(formComponent);
1069: } else {
1070: // Remove component's data (e.g. cookie)
1071: persister.clear(formComponent);
1072: }
1073: }
1074: }
1075: });
1076: }
1077: }
1078:
1079: /**
1080: * Method for dispatching/calling a interface on a page from the given url.
1081: * Used by {@link wicket.markup.html.form.Form#onFormSubmitted()} for
1082: * dispatching events
1083: *
1084: * @param page
1085: * The page where the event should be called on.
1086: * @param url
1087: * The url which describes the component path and the interface
1088: * to be called.
1089: */
1090: private void dispatchEvent(final Page page, final String url) {
1091: RequestCycle rc = RequestCycle.get();
1092: IRequestCycleProcessor processor = rc.getProcessor();
1093: final RequestParameters requestParameters = processor
1094: .getRequestCodingStrategy().decode(
1095: new FormDispatchRequest(rc.getRequest(), url));
1096: IRequestTarget rt = processor.resolve(rc, requestParameters);
1097: if (rt instanceof ListenerInterfaceRequestTarget) {
1098: ListenerInterfaceRequestTarget interfaceTarget = ((ListenerInterfaceRequestTarget) rt);
1099: interfaceTarget.getRequestListenerInterface().invoke(page,
1100: interfaceTarget.getTarget());
1101: } else {
1102: throw new WicketRuntimeException(
1103: "Attempt to access unknown request listener interface "
1104: + requestParameters.getInterfaceName());
1105: }
1106: }
1107:
1108: /**
1109: * Visits the form's children FormComponents and inform them that a new user
1110: * input is available in the Request
1111: */
1112: private void inputChanged() {
1113: visitFormComponents(new FormComponent.IVisitor() {
1114: public void formComponent(final FormComponent formComponent) {
1115: if (formComponent.isVisibleInHierarchy()) {
1116: formComponent.inputChanged();
1117: }
1118: }
1119: });
1120: }
1121:
1122: /**
1123: * This generates a piece of javascript code that sets the url in the
1124: * special hidden field and submits the form.
1125: *
1126: * Warning: This code should only be called in the rendering phase for form
1127: * components inside the form because it uses the css/javascript id of the
1128: * form which can be stored in the markup.
1129: *
1130: * @param url
1131: * The interface url that has to be stored in the hidden field
1132: * and submitted
1133: * @return The javascript code that submits the form.
1134: */
1135: public final CharSequence getJsForInterfaceUrl(CharSequence url) {
1136: return new AppendingStringBuffer("document.getElementById('")
1137: .append(getHiddenFieldId()).append("').value='")
1138: .append(url).append("';document.getElementById('")
1139: .append(getJavascriptId()).append("').submit();");
1140: }
1141:
1142: /**
1143: *
1144: */
1145: class FormDispatchRequest extends Request {
1146: private final Request realRequest;
1147:
1148: private final String url;
1149:
1150: private final Map params = new HashMap(4);
1151:
1152: /**
1153: * Construct.
1154: *
1155: * @param realRequest
1156: * @param url
1157: */
1158: public FormDispatchRequest(final Request realRequest,
1159: final String url) {
1160: this .realRequest = realRequest;
1161: this .url = realRequest.decodeURL(url);
1162:
1163: String queryPart = this .url
1164: .substring(this .url.indexOf("?") + 1);
1165: StringTokenizer paramsSt = new StringTokenizer(queryPart,
1166: "&");
1167: while (paramsSt.hasMoreTokens()) {
1168: String param = paramsSt.nextToken();
1169: int equalsSign = param.indexOf("=");
1170: if (equalsSign >= 0) {
1171: String paramName = param.substring(0, equalsSign);
1172: String value = param.substring(equalsSign + 1);
1173: params.put(paramName, value);
1174: } else {
1175: params.put(param, "");
1176: }
1177: }
1178: }
1179:
1180: /**
1181: * @see wicket.Request#getLocale()
1182: */
1183: public Locale getLocale() {
1184: return realRequest.getLocale();
1185: }
1186:
1187: /**
1188: * @see wicket.Request#getParameter(java.lang.String)
1189: */
1190: public String getParameter(String key) {
1191: return (String) params.get(key);
1192: }
1193:
1194: /**
1195: * @see wicket.Request#getParameterMap()
1196: */
1197: public Map getParameterMap() {
1198: return params;
1199: }
1200:
1201: /**
1202: * @see wicket.Request#getParameters(java.lang.String)
1203: */
1204: public String[] getParameters(String key) {
1205: String param = (String) params.get(key);
1206: if (param != null) {
1207: return new String[] { param };
1208: }
1209: return new String[0];
1210: }
1211:
1212: /**
1213: * @see wicket.Request#getPath()
1214: */
1215: public String getPath() {
1216: return realRequest.getPath();
1217: }
1218:
1219: /**
1220: * @see wicket.Request#getRelativeURL()
1221: */
1222: public String getRelativeURL() {
1223: int tmp = url.indexOf("/", 1);
1224: if (tmp != -1) {
1225: return url.substring(tmp);
1226: }
1227: return url;
1228: }
1229:
1230: /**
1231: * @see wicket.Request#getURL()
1232: */
1233: public String getURL() {
1234: return url;
1235: }
1236: }
1237:
1238: /**
1239: * Returns the prefix used when building validator keys. This allows a form
1240: * to use a separate "set" of keys. For example if prefix "short" is
1241: * returned, validator key short.RequiredValidator will be tried instead of
1242: * RequiredValidator key.
1243: * <p>
1244: * This can be useful when different designs are used for a form. In a form
1245: * where error messages are displayed next to their respective form
1246: * components as opposed to at the top of the form, the ${label} attribute
1247: * is of little use and only causes redundant information to appear in the
1248: * message. Forms like these can return the "short" (or any other string)
1249: * validator prefix and declare key: short.RequiredValidator=required to
1250: * override the longer message which is usually declared like this:
1251: * RequiredValidator=${label} is a required field
1252: * <p>
1253: * Returned prefix will be used for all form components. The prefix can also
1254: * be overridden on form component level by overriding
1255: * {@link FormComponent#getValidatorKeyPrefix()}
1256: *
1257: * @return prefix prepended to validator keys
1258: */
1259: public String getValidatorKeyPrefix() {
1260: return null;
1261: }
1262:
1263: /**
1264: * Adds a form validator to the form.
1265: *
1266: * @see IFormValidator
1267: * @param validator
1268: * validator
1269: */
1270: public void add(IFormValidator validator) {
1271: if (validator == null) {
1272: throw new IllegalArgumentException(
1273: "validator argument cannot be null");
1274: }
1275: formValidators_add(validator);
1276: }
1277:
1278: /**
1279: * @param validator
1280: * The form validator to add to the formValidators Object (which
1281: * may be an array of IFormValidators or a single instance, for
1282: * efficiency)
1283: */
1284: private void formValidators_add(final IFormValidator validator) {
1285: if (this .formValidators == null) {
1286: this .formValidators = validator;
1287: } else {
1288: // Get current list size
1289: final int size = formValidators_size();
1290:
1291: // Create array that holds size + 1 elements
1292: final IFormValidator[] validators = new IFormValidator[size + 1];
1293:
1294: // Loop through existing validators copying them
1295: for (int i = 0; i < size; i++) {
1296: validators[i] = formValidators_get(i);
1297: }
1298:
1299: // Add new validator to the end
1300: validators[size] = validator;
1301:
1302: // Save new validator list
1303: this .formValidators = validators;
1304: }
1305: }
1306:
1307: /**
1308: * Gets form validator from formValidators Object (which may be an array of
1309: * IFormValidators or a single instance, for efficiency) at the given index
1310: *
1311: * @param index
1312: * The index of the validator to get
1313: * @return The form validator
1314: */
1315: private IFormValidator formValidators_get(int index) {
1316: if (this .formValidators == null) {
1317: throw new IndexOutOfBoundsException();
1318: }
1319: if (this .formValidators instanceof IFormValidator[]) {
1320: return ((IFormValidator[]) formValidators)[index];
1321: }
1322: return (IFormValidator) formValidators;
1323: }
1324:
1325: /**
1326: * @return The number of form validators in the formValidators Object (which
1327: * may be an array of IFormValidators or a single instance, for
1328: * efficiency)
1329: */
1330: private int formValidators_size() {
1331: if (this .formValidators == null) {
1332: return 0;
1333: }
1334: if (this .formValidators instanceof IFormValidator[]) {
1335: return ((IFormValidator[]) formValidators).length;
1336: }
1337: return 1;
1338: }
1339:
1340: /**
1341: * /** Registers an error feedback message for this component
1342: *
1343: * @param error
1344: * error message
1345: * @param args
1346: * argument replacement map for ${key} variables
1347: */
1348: public final void error(String error, Map args) {
1349: error(new MapVariableInterpolator(error, args).toString());
1350: }
1351:
1352: }
|