Author: ehillenius Date: Tue Jun 5 08:25:47 2007 New Revision: 544517 URL: http://svn.apache.org/viewvc?view=rev&rev=544517 Log: formatted members
Modified: incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/markup/html/form/Form.java Modified: incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/markup/html/form/Form.java URL: http://svn.apache.org/viewvc/incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/markup/html/form/Form.java?view=diff&rev=544517&r1=544516&r2=544517 ============================================================================== --- incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/markup/html/form/Form.java (original) +++ incubator/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/markup/html/form/Form.java Tue Jun 5 08:25:47 2007 @@ -138,16 +138,6 @@ public class Form extends WebMarkupContainer implements IFormSubmitListener { /** - * Constant for specifying how a form is submitted, in this case using post. - */ - public static final String METHOD_POST = "post"; - - /** - * Constant for specifying how a form is submitted, in this case using get. - */ - public static final String METHOD_GET = "get"; - - /** * Visitor used for validation * * @author Igor Vaynberg (ivaynberg) @@ -186,28 +176,117 @@ public abstract void validate(FormComponent formComponent); } - private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge"; + /** + * + */ + class FormDispatchRequest extends Request + { + private final ValueMap params = new ValueMap(); - private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed"; + private final Request realRequest; + + private final String url; + + /** + * Construct. + * + * @param realRequest + * @param url + */ + public FormDispatchRequest(final Request realRequest, final String url) + { + this.realRequest = realRequest; + this.url = realRequest.decodeURL(url); + + String queryString = this.url.substring(this.url.indexOf("?") + 1); + RequestUtils.decodeParameters(queryString, params); + } + + /** + * @see org.apache.wicket.Request#getLocale() + */ + public Locale getLocale() + { + return realRequest.getLocale(); + } + + /** + * @see org.apache.wicket.Request#getParameter(java.lang.String) + */ + public String getParameter(String key) + { + return (String)params.get(key); + } + + /** + * @see org.apache.wicket.Request#getParameterMap() + */ + public Map getParameterMap() + { + return params; + } + + /** + * @see org.apache.wicket.Request#getParameters(java.lang.String) + */ + public String[] getParameters(String key) + { + String param = (String)params.get(key); + if (param != null) + { + return new String[] { param }; + } + return new String[0]; + } + + /** + * @see org.apache.wicket.Request#getPath() + */ + public String getPath() + { + return realRequest.getPath(); + } + + public String getRelativePathPrefixToContextRoot() + { + return realRequest.getRelativePathPrefixToContextRoot(); + } + + public String getRelativePathPrefixToWicketHandler() + { + return realRequest.getRelativePathPrefixToWicketHandler(); + } + + /** + * @see org.apache.wicket.Request#getURL() + */ + public String getURL() + { + return url; + } + } + + /** + * Constant for specifying how a form is submitted, in this case using get. + */ + public static final String METHOD_GET = "get"; + + /** + * Constant for specifying how a form is submitted, in this case using post. + */ + public static final String METHOD_POST = "post"; /** Flag that indicates this form has been submitted during this request */ private static final short FLAG_SUBMITTED = FLAG_RESERVED1; - private static final long serialVersionUID = 1L; - /** Log. */ private static final Logger log = LoggerFactory.getLogger(Form.class); - /** Maximum size of an upload in bytes */ - private Bytes maxSize = Bytes.MAX; - - /** True if the form has enctype of multipart/form-data */ - private boolean multiPart = false; + private static final long serialVersionUID = 1L; - private String javascriptId; + private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed"; - /** multi-validators assigned to this form */ - private Object formValidators = null; + private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge"; /** * Any default button. If set, a hidden submit button will be rendered right @@ -222,6 +301,17 @@ */ private Button defaultButton; + /** multi-validators assigned to this form */ + private Object formValidators = null; + + private String javascriptId; + + /** Maximum size of an upload in bytes */ + private Bytes maxSize = Bytes.MAX; + + /** True if the form has enctype of multipart/form-data */ + private boolean multiPart = false; + /** * Constructs a form with no validation. * @@ -246,19 +336,101 @@ } /** - * Gets the method used to submit the form. Defaults to 'post'. Override - * this if you have a requirement to alter this behavior. + * Adds a form validator to the form. * - * @return the method used to submit the form. + * @param validator + * validator + * @throws IllegalArgumentException + * if validator is null + * @see IFormValidator + * @see IValidatorAddListener */ - protected String getMethod() + public void add(IFormValidator validator) { - return METHOD_POST; + if (validator == null) + { + throw new IllegalArgumentException("validator argument cannot be null"); + } + + // add the validator + formValidators_add(validator); + + // see whether the validator listens for add events + if (validator instanceof IValidatorAddListener) + { + ((IValidatorAddListener)validator).onAdded(this); + } } - protected boolean getStatelessHint() + /** + * Clears the input from the form's nested children of type + * [EMAIL PROTECTED] FormComponent}. This method is typically called when a form needs + * to be reset. + */ + public final void clearInput() { - return false; + // Visit all the (visible) form components and clear the input on each. + visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + { + public void onFormComponent(final FormComponent formComponent) + { + if (formComponent.isVisibleInHierarchy()) + { + // Clear input from form component + formComponent.clearInput(); + } + } + }); + } + + /** + * /** Registers an error feedback message for this component + * + * @param error + * error message + * @param args + * argument replacement map for ${key} variables + */ + public final void error(String error, Map args) + { + error(new MapVariableInterpolator(error, args).toString()); + } + + /** + * Gets the button which submitted this form. + * + * @return The button which submitted this form or null if the processing + * was not trigger by a registered button component + */ + public final IFormSubmittingComponent findSubmittingButton() + { + IFormSubmittingComponent submittingButton = (IFormSubmittingComponent)getPage() + .visitChildren(IFormSubmittingComponent.class, new IVisitor() + { + public Object component(final Component component) + { + // Get button + final IFormSubmittingComponent submit = (IFormSubmittingComponent)component; + + // Check for button-name or button-name.x request string + if (submit.getForm() != null + && submit.getForm().getRootForm() == Form.this + && (getRequest().getParameter(submit.getInputName()) != null || getRequest() + .getParameter(submit.getInputName() + ".x") != null)) + { + if (!component.isVisible()) + { + throw new WicketRuntimeException("Submit Button " + + submit.getInputName() + " (path=" + + component.getPageRelativePath() + ") is not visible"); + } + return submit; + } + return CONTINUE_TRAVERSAL; + } + }); + + return submittingButton; } /** @@ -291,6 +463,26 @@ } /** + * This generates a piece of javascript code that sets the url in the + * special hidden field and submits the form. + * + * Warning: This code should only be called in the rendering phase for form + * components inside the form because it uses the css/javascript id of the + * form which can be stored in the markup. + * + * @param url + * The interface url that has to be stored in the hidden field + * and submitted + * @return The javascript code that submits the form. + */ + public final CharSequence getJsForInterfaceUrl(CharSequence url) + { + return new AppendingStringBuffer("document.getElementById('").append(getHiddenFieldId()) + .append("').value='").append(url).append("';document.getElementById('").append( + getJavascriptId()).append("').submit();"); + } + + /** * @return the maxSize of uploaded files */ public Bytes getMaxSize() @@ -299,12 +491,107 @@ } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. - * <p> - * Retrieves FormComponent values related to the page using the persister - * and assign the values to the FormComponent. Thus initializing them. - */ - public final void loadPersistentFormComponentValues() + * Returns the root form or this, if this is the root form. + * + * @return root form or this form + */ + public Form getRootForm() + { + Form form; + Form parent = this; + do + { + form = parent; + parent = (Form)form.findParent(Form.class); + } + while (parent != null); + + return form; + } + + /** + * Returns the prefix used when building validator keys. This allows a form + * to use a separate "set" of keys. For example if prefix "short" is + * returned, validator key short.Required will be tried instead of Required + * key. + * <p> + * This can be useful when different designs are used for a form. In a form + * where error messages are displayed next to their respective form + * components as opposed to at the top of the form, the ${label} attribute + * is of little use and only causes redundant information to appear in the + * message. Forms like these can return the "short" (or any other string) + * validator prefix and declare key: short.Required=required to override the + * longer message which is usually declared like this: Required=${label} is + * a required field + * <p> + * Returned prefix will be used for all form components. The prefix can also + * be overridden on form component level by overriding + * [EMAIL PROTECTED] FormComponent#getValidatorKeyPrefix()} + * + * @return prefix prepended to validator keys + */ + public String getValidatorKeyPrefix() + { + return null; + } + + /** + * Gets whether the current form has any error registered. + * + * @return True if this form has at least one error. + */ + public final boolean hasError() + { + // if this form itself has an error message + if (hasErrorMessage()) + { + return true; + } + + // the form doesn't have any errors, now check any nested form + // components + return anyFormComponentError(); + } + + /** + * Returns whether the form is a root form, which means that there's no + * other form in it's parent hierarchy. + * + * @return true if form is a root form, false otherwise + */ + public boolean isRootForm() + { + return findParent(Form.class) == null; + } + + /** + * Checks if this form has been submitted during the current request + * + * @return true if the form has been submitted during this request, false + * otherwise + */ + public final boolean isSubmitted() + { + return getFlag(FLAG_SUBMITTED); + } + + /** + * Method made final because we want to ensure users call setVersioned. + * + * @see org.apache.wicket.Component#isVersioned() + */ + public boolean isVersioned() + { + return super.isVersioned(); + } + + /** + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. + * <p> + * Retrieves FormComponent values related to the page using the persister + * and assign the values to the FormComponent. Thus initializing them. + */ + public final void loadPersistentFormComponentValues() { visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() { @@ -390,25 +677,48 @@ } /** - * Checks if this form has been submitted during the current request + * Process the form. Though you can override this method to provide your + * whole own algorithm, it is not recommended to do so. + * <p> + * See the class documentation for further details on the form processing + * </p> * - * @return true if the form has been submitted during this request, false - * otherwise + * @return False if the form had an error */ - public final boolean isSubmitted() + public boolean process() { - return getFlag(FLAG_SUBMITTED); - } + // run validation + validate(); - /** - * @see org.apache.wicket.Component#onDetach() - */ - protected void onDetach() - { - super.internalOnDetach(); - setFlag(FLAG_SUBMITTED, false); + // If a validation error occurred + if (hasError()) + { + // mark all children as invalid + markFormComponentsInvalid(); - super.onDetach(); + // let subclass handle error + onError(); + + // Form has an error + return false; + } + else + { + // mark all childeren as valid + markFormComponentsValid(); + + // before updating, call the interception method for clients + beforeUpdateFormComponentModels(); + + // Update model using form data + updateFormComponentModels(); + + // Persist FormComponents if requested + persistFormComponentData(); + + // Form has no error + return true; + } } /** @@ -519,25 +829,21 @@ } /** - * Method made final because we want to ensure users call setVersioned. - * - * @see org.apache.wicket.Component#isVersioned() - */ - public boolean isVersioned() - { - return super.isVersioned(); - } - - /** * Convenient and typesafe way to visit all the form components on a form - * postorder (deepest first) * * @param visitor * The visitor interface to call */ - public final void visitFormComponentsPostOrder(final FormComponent.IVisitor visitor) + public final void visitFormComponents(final FormComponent.IVisitor visitor) { - FormComponent.visitFormComponentsPostOrder(this, visitor); + visitChildren(FormComponent.class, new IVisitor() + { + public Object component(final Component component) + { + visitor.formComponent((FormComponent)component); + return CONTINUE_TRAVERSAL; + } + }); /** * TODO Post 1.2 General: Maybe we should re-think how Borders are @@ -565,20 +871,14 @@ /** * Convenient and typesafe way to visit all the form components on a form + * postorder (deepest first) * * @param visitor * The visitor interface to call */ - public final void visitFormComponents(final FormComponent.IVisitor visitor) + public final void visitFormComponentsPostOrder(final FormComponent.IVisitor visitor) { - visitChildren(FormComponent.class, new IVisitor() - { - public Object component(final Component component) - { - visitor.formComponent((FormComponent)component); - return CONTINUE_TRAVERSAL; - } - }); + FormComponent.visitFormComponentsPostOrder(this, visitor); /** * TODO Post 1.2 General: Maybe we should re-think how Borders are @@ -605,264 +905,452 @@ } /** - * If a default button was set on this form, this method will be called to - * render an extra field with an invisible style so that pressing enter in - * one of the textfields will do a form submit using this button. This - * method is overridable as what we do is best effort only, and may not what - * you want in specific situations. So if you have specific usability - * concerns, or want to follow another strategy, you may override this - * method. + * Find out whether there is any registered error for a form component. * - * @param markupStream - * The markup stream - * @param openTag - * The open tag for the body + * @return whether there is any registered error for a form component */ - protected void appendDefaultButtonField(final MarkupStream markupStream, - final ComponentTag openTag) + private boolean anyFormComponentError() { - String nameAndId = getHiddenFieldId(); - AppendingStringBuffer buffer = new AppendingStringBuffer(); - // get the value, first seeing whether the value attribute is set - // by a model - String value = defaultButton.getModelObjectAsString(); - if (value == null || "".equals(value)) + final Object value = visitChildren(new IVisitor() { - // nope it isn't; try to read from the attributes - // note that we're only trying lower case here - value = defaultButton.getMarkupAttributes().getString("value"); - } + public Object component(final Component component) + { + if (component.hasErrorMessage()) + { + return STOP_TRAVERSAL; + } - // append the button - buffer.append("<input type=\"submit\" value=\"").append(value).append("\" name=\"").append( - defaultButton.getInputName()).append("\""); - buffer.append("style=\"width: 0px; height: 0px; position: absolute; left:-100px;\""); - buffer.append(" />"); - getResponse().write(buffer); - } + // Traverse all children + return CONTINUE_TRAVERSAL; + } + }); - /** - * Template method to allow clients to do any processing (like recording the - * current model so that, in case onSubmit does further validation, the - * model can be rolled back) before the actual updating of form component - * models is done. - */ - protected void beforeUpdateFormComponentModels() - { + return value == IVisitor.STOP_TRAVERSAL ? true : false; } /** - * Called (by the default implementation of 'process') when all fields - * validated, the form was updated and it's data was allowed to be - * persisted. It is meant for delegating further processing to clients. - * <p> - * This implementation first finds out whether the form processing was - * triggered by a nested button of this form. If that is the case, that - * button's onSubmit is called first. - * </p> - * <p> - * Regardless of whether a submitting button was found, the form's onSubmit - * method is called next. - * </p> + * Method for dispatching/calling a interface on a page from the given url. + * Used by [EMAIL PROTECTED] org.apache.wicket.markup.html.form.Form#onFormSubmitted()} + * for dispatching events * - * @param submittingButton - * the button that triggered this form processing, or null if the - * processing was triggered by something else (like a non-Wicket - * submit button or a javascript execution) - */ - protected void delegateSubmit(IFormSubmittingComponent submittingButton) - { - // when the given button is not null, it means that it was the - // submitting button - if (submittingButton != null) + * @param page + * The page where the event should be called on. + * @param url + * The url which describes the component path and the interface + * to be called. + */ + private void dispatchEvent(final Page page, final String url) + { + RequestCycle rc = RequestCycle.get(); + IRequestCycleProcessor processor = rc.getProcessor(); + final RequestParameters requestParameters = processor.getRequestCodingStrategy().decode( + new FormDispatchRequest(rc.getRequest(), url)); + IRequestTarget rt = processor.resolve(rc, requestParameters); + if (rt instanceof ListenerInterfaceRequestTarget) { - submittingButton.onSubmit(); + ListenerInterfaceRequestTarget interfaceTarget = ((ListenerInterfaceRequestTarget)rt); + interfaceTarget.getRequestListenerInterface().invoke(page, interfaceTarget.getTarget()); + } + else + { + throw new WicketRuntimeException( + "Attempt to access unknown request listener interface " + + requestParameters.getInterfaceName()); } - - // Model was successfully updated with valid data - onSubmit(); } /** - * Gets the button which submitted this form. - * - * @return The button which submitted this form or null if the processing - * was not trigger by a registered button component + * @param validator + * The form validator to add to the formValidators Object (which + * may be an array of IFormValidators or a single instance, for + * efficiency) */ - public final IFormSubmittingComponent findSubmittingButton() + private void formValidators_add(final IFormValidator validator) { - IFormSubmittingComponent submittingButton = (IFormSubmittingComponent)getPage() - .visitChildren(IFormSubmittingComponent.class, new IVisitor() - { - public Object component(final Component component) - { - // Get button - final IFormSubmittingComponent submit = (IFormSubmittingComponent)component; + if (this.formValidators == null) + { + this.formValidators = validator; + } + else + { + // Get current list size + final int size = formValidators_size(); - // Check for button-name or button-name.x request string - if (submit.getForm() != null - && submit.getForm().getRootForm() == Form.this - && (getRequest().getParameter(submit.getInputName()) != null || getRequest() - .getParameter(submit.getInputName() + ".x") != null)) - { - if (!component.isVisible()) - { - throw new WicketRuntimeException("Submit Button " - + submit.getInputName() + " (path=" - + component.getPageRelativePath() + ") is not visible"); - } - return submit; - } - return CONTINUE_TRAVERSAL; - } - }); + // Create array that holds size + 1 elements + final IFormValidator[] validators = new IFormValidator[size + 1]; - return submittingButton; - } + // Loop through existing validators copying them + for (int i = 0; i < size; i++) + { + validators[i] = formValidators_get(i); + } - /** - * Gets the form component persistence manager; it is lazy loaded. - * - * @return The form component value persister - */ - protected IValuePersister getValuePersister() - { - return new CookieValuePersister(); + // Add new validator to the end + validators[size] = validator; + + // Save new validator list + this.formValidators = validators; + } } /** - * Gets whether the current form has any error registered. + * Gets form validator from formValidators Object (which may be an array of + * IFormValidators or a single instance, for efficiency) at the given index * - * @return True if this form has at least one error. + * @param index + * The index of the validator to get + * @return The form validator */ - public final boolean hasError() + private IFormValidator formValidators_get(int index) { - // if this form itself has an error message - if (hasErrorMessage()) + if (this.formValidators == null) { - return true; + throw new IndexOutOfBoundsException(); } - - // the form doesn't have any errors, now check any nested form - // components - return anyFormComponentError(); + if (this.formValidators instanceof IFormValidator[]) + { + return ((IFormValidator[])formValidators)[index]; + } + return (IFormValidator)formValidators; } /** - * @see org.apache.wicket.Component#internalOnModelChanged() + * @return The number of form validators in the formValidators Object (which + * may be an array of IFormValidators or a single instance, for + * efficiency) */ - protected void internalOnModelChanged() + private int formValidators_size() { - // Visit all the form components and validate each - visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + if (this.formValidators == null) { - public void onFormComponent(final FormComponent formComponent) - { - // If form component is using form model - if (formComponent.sameInnermostModel(Form.this)) - { - formComponent.modelChanged(); - } - } - }); + return 0; + } + if (this.formValidators instanceof IFormValidator[]) + { + return ((IFormValidator[])formValidators).length; + } + return 1; } /** - * Mark each form component on this form invalid. + * Visits the form's children FormComponents and inform them that a new user + * input is available in the Request */ - protected final void markFormComponentsInvalid() + private void inputChanged() { - // call invalidate methods of all nested form components visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() { public void onFormComponent(final FormComponent formComponent) { if (formComponent.isVisibleInHierarchy()) { - formComponent.invalid(); + formComponent.inputChanged(); } } }); } /** - * Mark each form component on this form valid. + * Persist (e.g. Cookie) FormComponent data to be reloaded and re-assigned + * to the FormComponent automatically when the page is visited by the user + * next time. + * + * @see org.apache.wicket.markup.html.form.FormComponent#updateModel() */ - protected final void markFormComponentsValid() + private void persistFormComponentData() { - // call invalidate methods of all nested form components - visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + // Cannot add cookies to request cycle unless it accepts them + // We could conceivably be HTML over some other protocol! + if (getRequestCycle() instanceof WebRequestCycle) { - public void onFormComponent(final FormComponent formComponent) + // The persistence manager responsible to persist and retrieve + // FormComponent data + final IValuePersister persister = getValuePersister(); + + // Search for FormComponent children. Ignore all other + visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() { - if (formComponent.isVisibleInHierarchy()) + public void onFormComponent(final FormComponent formComponent) { - formComponent.valid(); + if (formComponent.isVisibleInHierarchy()) + { + // If peristence is switched on for that FormComponent + // ... + if (formComponent.isPersistent()) + { + // Save component's data (e.g. in a cookie) + persister.save(formComponent); + } + else + { + // Remove component's data (e.g. cookie) + persister.clear(formComponent); + } + } } - } - }); - } - - /** - * Returns the HiddenFieldId which will be used as the name and id property - * of the hiddenfield that is generated for event dispatches. - * - * @return The name and id of the hidden field. - */ - protected final String getHiddenFieldId() - { - return getJavascriptId() + "_hf_0"; - } - - /** - * Returns the javascript/css id of this form that will be used to generated - * the id="xxx" attribute. it will be generated if not set already in the - * onComponentTag. Where it will be tried to load from the markup first - * before it is generated. - * - * @return The javascript/css id of this form. - */ - protected final String getJavascriptId() - { - if (Strings.isEmpty(javascriptId)) - { - javascriptId = getMarkupId(); + }); } - return javascriptId; } /** - * Append an additional hidden input tag to support anchor tags that can - * submit a form. + * If a default button was set on this form, this method will be called to + * render an extra field with an invisible style so that pressing enter in + * one of the textfields will do a form submit using this button. This + * method is overridable as what we do is best effort only, and may not what + * you want in specific situations. So if you have specific usability + * concerns, or want to follow another strategy, you may override this + * method. * * @param markupStream * The markup stream * @param openTag * The open tag for the body */ - protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) + protected void appendDefaultButtonField(final MarkupStream markupStream, + final ComponentTag openTag) { - if (isRootForm()) + String nameAndId = getHiddenFieldId(); + AppendingStringBuffer buffer = new AppendingStringBuffer(); + // get the value, first seeing whether the value attribute is set + // by a model + String value = defaultButton.getModelObjectAsString(); + if (value == null || "".equals(value)) { - // get the hidden field id - String nameAndId = getHiddenFieldId(); - - // render the hidden field - AppendingStringBuffer buffer = new AppendingStringBuffer( - "<div style=\"display:none\"><input type=\"hidden\" name=\"").append(nameAndId) - .append("\" id=\"").append(nameAndId).append("\" /></div>"); - getResponse().write(buffer); - - // if a default button was set, handle the rendering of that - if (defaultButton != null && defaultButton.isVisibleInHierarchy() - && defaultButton.isEnabled()) - { - appendDefaultButtonField(markupStream, openTag); - } + // nope it isn't; try to read from the attributes + // note that we're only trying lower case here + value = defaultButton.getMarkupAttributes().getString("value"); } - // do the rest of the processing - super.onComponentTagBody(markupStream, openTag); + // append the button + buffer.append("<input type=\"submit\" value=\"").append(value).append("\" name=\"").append( + defaultButton.getInputName()).append("\""); + buffer.append("style=\"width: 0px; height: 0px; position: absolute; left:-100px;\""); + buffer.append(" />"); + getResponse().write(buffer); + } + + /** + * Template method to allow clients to do any processing (like recording the + * current model so that, in case onSubmit does further validation, the + * model can be rolled back) before the actual updating of form component + * models is done. + */ + protected void beforeUpdateFormComponentModels() + { + } + + /** + * Called (by the default implementation of 'process') when all fields + * validated, the form was updated and it's data was allowed to be + * persisted. It is meant for delegating further processing to clients. + * <p> + * This implementation first finds out whether the form processing was + * triggered by a nested button of this form. If that is the case, that + * button's onSubmit is called first. + * </p> + * <p> + * Regardless of whether a submitting button was found, the form's onSubmit + * method is called next. + * </p> + * + * @param submittingButton + * the button that triggered this form processing, or null if the + * processing was triggered by something else (like a non-Wicket + * submit button or a javascript execution) + */ + protected void delegateSubmit(IFormSubmittingComponent submittingButton) + { + // when the given button is not null, it means that it was the + // submitting button + if (submittingButton != null) + { + submittingButton.onSubmit(); + } + + // Model was successfully updated with valid data + onSubmit(); + } + + /** + * Returns the HiddenFieldId which will be used as the name and id property + * of the hiddenfield that is generated for event dispatches. + * + * @return The name and id of the hidden field. + */ + protected final String getHiddenFieldId() + { + return getJavascriptId() + "_hf_0"; + } + + /** + * Returns the javascript/css id of this form that will be used to generated + * the id="xxx" attribute. it will be generated if not set already in the + * onComponentTag. Where it will be tried to load from the markup first + * before it is generated. + * + * @return The javascript/css id of this form. + */ + protected final String getJavascriptId() + { + if (Strings.isEmpty(javascriptId)) + { + javascriptId = getMarkupId(); + } + return javascriptId; + } + + + /** + * Gets the method used to submit the form. Defaults to 'post'. Override + * this if you have a requirement to alter this behavior. + * + * @return the method used to submit the form. + */ + protected String getMethod() + { + return METHOD_POST; + } + + protected boolean getStatelessHint() + { + return false; + } + + /** + * Gets the form component persistence manager; it is lazy loaded. + * + * @return The form component value persister + */ + protected IValuePersister getValuePersister() + { + return new CookieValuePersister(); + } + + /** + * Handles multi-part processing of the submitted data. + * + * WARNING + * + * If this method is overridden it can break [EMAIL PROTECTED] FileUploadField}s on + * this form + * + * @return false if form is multipart and upload failed + */ + protected boolean handleMultiPart() + { + if (multiPart) + { + // Change the request to a multipart web request so parameters are + // parsed out correctly + try + { + final WebRequest multipartWebRequest = ((WebRequest)getRequest()) + .newMultipartWebRequest(this.maxSize); + getRequestCycle().setRequest(multipartWebRequest); + } + catch (WicketRuntimeException wre) + { + if (wre.getCause() == null || !(wre.getCause() instanceof FileUploadException)) + { + throw wre; + } + + FileUploadException e = (FileUploadException)wre.getCause(); + // Create model with exception and maximum size values + final Map model = new HashMap(); + model.put("exception", e); + model.put("maxSize", maxSize); + + if (e instanceof SizeLimitExceededException) + { + // Resource key should be <form-id>.uploadTooLarge to + // override default message + final String defaultValue = "Upload must be less than " + maxSize; + String msg = getString(getId() + "." + UPLOAD_TOO_LARGE_RESOURCE_KEY, Model + .valueOf(model), defaultValue); + error(msg); + + if (log.isDebugEnabled()) + { + log.error(msg, e); + } + else + { + log.error(msg); + } + } + else + { + // Resource key should be <form-id>.uploadFailed to override + // default message + final String defaultValue = "Upload failed: " + e.getLocalizedMessage(); + String msg = getString(getId() + "." + UPLOAD_FAILED_RESOURCE_KEY, Model + .valueOf(model), defaultValue); + error(msg); + + log.error(msg, e); + } + + // don't process the form if there is a FileUploadException + return false; + } + } + return true; + } + + /** + * @see org.apache.wicket.Component#internalOnModelChanged() + */ + protected void internalOnModelChanged() + { + // Visit all the form components and validate each + visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + { + public void onFormComponent(final FormComponent formComponent) + { + // If form component is using form model + if (formComponent.sameInnermostModel(Form.this)) + { + formComponent.modelChanged(); + } + } + }); + } + + /** + * Mark each form component on this form invalid. + */ + protected final void markFormComponentsInvalid() + { + // call invalidate methods of all nested form components + visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + { + public void onFormComponent(final FormComponent formComponent) + { + if (formComponent.isVisibleInHierarchy()) + { + formComponent.invalid(); + } + } + }); + } + + /** + * Mark each form component on this form valid. + */ + protected final void markFormComponentsValid() + { + // call invalidate methods of all nested form components + visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() + { + public void onFormComponent(final FormComponent formComponent) + { + if (formComponent.isVisibleInHierarchy()) + { + formComponent.valid(); + } + } + }); } /** @@ -924,83 +1412,83 @@ } /** - * Method to override if you want to do something special when an error - * occurs (other than simply displaying validation errors). - */ - protected void onError() - { - } - - /** - * @see org.apache.wicket.Component#onRender(MarkupStream) + * Append an additional hidden input tag to support anchor tags that can + * submit a form. + * + * @param markupStream + * The markup stream + * @param openTag + * The open tag for the body */ - protected void onRender(final MarkupStream markupStream) + protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) { - // Force multi-part on if any child form component is multi-part - visitFormComponents(new FormComponent.AbstractVisitor() + if (isRootForm()) { - public void onFormComponent(FormComponent formComponent) - { - if (formComponent.isVisible() && formComponent.isMultiPart()) - { - setMultiPart(true); - } + // get the hidden field id + String nameAndId = getHiddenFieldId(); + + // render the hidden field + AppendingStringBuffer buffer = new AppendingStringBuffer( + "<div style=\"display:none\"><input type=\"hidden\" name=\"").append(nameAndId) + .append("\" id=\"").append(nameAndId).append("\" /></div>"); + getResponse().write(buffer); + + // if a default button was set, handle the rendering of that + if (defaultButton != null && defaultButton.isVisibleInHierarchy() + && defaultButton.isEnabled()) + { + appendDefaultButtonField(markupStream, openTag); } - }); + } - super.onRender(markupStream); + // do the rest of the processing + super.onComponentTagBody(markupStream, openTag); } /** - * Implemented by subclasses to deal with form submits. + * @see org.apache.wicket.Component#onDetach() */ - protected void onSubmit() + protected void onDetach() { + super.internalOnDetach(); + setFlag(FLAG_SUBMITTED, false); + + super.onDetach(); } /** - * Process the form. Though you can override this method to provide your - * whole own algorithm, it is not recommended to do so. - * <p> - * See the class documentation for further details on the form processing - * </p> - * - * @return False if the form had an error + * Method to override if you want to do something special when an error + * occurs (other than simply displaying validation errors). */ - public boolean process() + protected void onError() { - // run validation - validate(); - - // If a validation error occurred - if (hasError()) - { - // mark all children as invalid - markFormComponentsInvalid(); - - // let subclass handle error - onError(); + } - // Form has an error - return false; - } - else + /** + * @see org.apache.wicket.Component#onRender(MarkupStream) + */ + protected void onRender(final MarkupStream markupStream) + { + // Force multi-part on if any child form component is multi-part + visitFormComponents(new FormComponent.AbstractVisitor() { - // mark all childeren as valid - markFormComponentsValid(); - - // before updating, call the interception method for clients - beforeUpdateFormComponentModels(); - - // Update model using form data - updateFormComponentModels(); + public void onFormComponent(FormComponent formComponent) + { + if (formComponent.isVisible() && formComponent.isMultiPart()) + { + setMultiPart(true); + } + } + }); - // Persist FormComponents if requested - persistFormComponentData(); + super.onRender(markupStream); + } - // Form has no error - return true; - } + /** + * Implemented by subclasses to deal with form submits. + */ + protected void onSubmit() + { } /** @@ -1022,27 +1510,6 @@ } /** - * Clears the input from the form's nested children of type - * [EMAIL PROTECTED] FormComponent}. This method is typically called when a form needs - * to be reset. - */ - public final void clearInput() - { - // Visit all the (visible) form components and clear the input on each. - visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() - { - public void onFormComponent(final FormComponent formComponent) - { - if (formComponent.isVisibleInHierarchy()) - { - // Clear input from form component - formComponent.clearInput(); - } - } - }); - } - - /** * Validates the form by checking required fields, converting raw input and * running validators for every form component, and last running global form * validators. This method is typically called before updating any models. @@ -1057,7 +1524,6 @@ validateFormValidators(); } - /** * Triggers type conversion on form components */ @@ -1073,18 +1539,6 @@ } /** - * Triggers any added [EMAIL PROTECTED] IFormValidator}s. - */ - protected final void validateFormValidators() - { - final int count = formValidators_size(); - for (int i = 0; i < count; i++) - { - validateFormValidator(formValidators_get(i)); - } - } - - /** * Validates form with the given form validator * * @param validator @@ -1120,468 +1574,14 @@ } /** - * Find out whether there is any registered error for a form component. - * - * @return whether there is any registered error for a form component - */ - private boolean anyFormComponentError() - { - final Object value = visitChildren(new IVisitor() - { - public Object component(final Component component) - { - if (component.hasErrorMessage()) - { - return STOP_TRAVERSAL; - } - - // Traverse all children - return CONTINUE_TRAVERSAL; - } - }); - - return value == IVisitor.STOP_TRAVERSAL ? true : false; - } - - /** - * Handles multi-part processing of the submitted data. - * - * WARNING - * - * If this method is overridden it can break [EMAIL PROTECTED] FileUploadField}s on - * this form - * - * @return false if form is multipart and upload failed + * Triggers any added [EMAIL PROTECTED] IFormValidator}s. */ - protected boolean handleMultiPart() + protected final void validateFormValidators() { - if (multiPart) + final int count = formValidators_size(); + for (int i = 0; i < count; i++) { - // Change the request to a multipart web request so parameters are - // parsed out correctly - try - { - final WebRequest multipartWebRequest = ((WebRequest)getRequest()) - .newMultipartWebRequest(this.maxSize); - getRequestCycle().setRequest(multipartWebRequest); - } - catch (WicketRuntimeException wre) - { - if (wre.getCause() == null || !(wre.getCause() instanceof FileUploadException)) - { - throw wre; - } - - FileUploadException e = (FileUploadException)wre.getCause(); - // Create model with exception and maximum size values - final Map model = new HashMap(); - model.put("exception", e); - model.put("maxSize", maxSize); - - if (e instanceof SizeLimitExceededException) - { - // Resource key should be <form-id>.uploadTooLarge to - // override default message - final String defaultValue = "Upload must be less than " + maxSize; - String msg = getString(getId() + "." + UPLOAD_TOO_LARGE_RESOURCE_KEY, Model - .valueOf(model), defaultValue); - error(msg); - - if (log.isDebugEnabled()) - { - log.error(msg, e); - } - else - { - log.error(msg); - } - } - else - { - // Resource key should be <form-id>.uploadFailed to override - // default message - final String defaultValue = "Upload failed: " + e.getLocalizedMessage(); - String msg = getString(getId() + "." + UPLOAD_FAILED_RESOURCE_KEY, Model - .valueOf(model), defaultValue); - error(msg); - - log.error(msg, e); - } - - // don't process the form if there is a FileUploadException - return false; - } + validateFormValidator(formValidators_get(i)); } - return true; - } - - /** - * Persist (e.g. Cookie) FormComponent data to be reloaded and re-assigned - * to the FormComponent automatically when the page is visited by the user - * next time. - * - * @see org.apache.wicket.markup.html.form.FormComponent#updateModel() - */ - private void persistFormComponentData() - { - // Cannot add cookies to request cycle unless it accepts them - // We could conceivably be HTML over some other protocol! - if (getRequestCycle() instanceof WebRequestCycle) - { - // The persistence manager responsible to persist and retrieve - // FormComponent data - final IValuePersister persister = getValuePersister(); - - // Search for FormComponent children. Ignore all other - visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() - { - public void onFormComponent(final FormComponent formComponent) - { - if (formComponent.isVisibleInHierarchy()) - { - // If peristence is switched on for that FormComponent - // ... - if (formComponent.isPersistent()) - { - // Save component's data (e.g. in a cookie) - persister.save(formComponent); - } - else - { - // Remove component's data (e.g. cookie) - persister.clear(formComponent); - } - } - } - }); - } - } - - /** - * Method for dispatching/calling a interface on a page from the given url. - * Used by [EMAIL PROTECTED] org.apache.wicket.markup.html.form.Form#onFormSubmitted()} - * for dispatching events - * - * @param page - * The page where the event should be called on. - * @param url - * The url which describes the component path and the interface - * to be called. - */ - private void dispatchEvent(final Page page, final String url) - { - RequestCycle rc = RequestCycle.get(); - IRequestCycleProcessor processor = rc.getProcessor(); - final RequestParameters requestParameters = processor.getRequestCodingStrategy().decode( - new FormDispatchRequest(rc.getRequest(), url)); - IRequestTarget rt = processor.resolve(rc, requestParameters); - if (rt instanceof ListenerInterfaceRequestTarget) - { - ListenerInterfaceRequestTarget interfaceTarget = ((ListenerInterfaceRequestTarget)rt); - interfaceTarget.getRequestListenerInterface().invoke(page, interfaceTarget.getTarget()); - } - else - { - throw new WicketRuntimeException( - "Attempt to access unknown request listener interface " - + requestParameters.getInterfaceName()); - } - } - - /** - * Visits the form's children FormComponents and inform them that a new user - * input is available in the Request - */ - private void inputChanged() - { - visitFormComponentsPostOrder(new FormComponent.AbstractVisitor() - { - public void onFormComponent(final FormComponent formComponent) - { - if (formComponent.isVisibleInHierarchy()) - { - formComponent.inputChanged(); - } - } - }); - } - - /** - * This generates a piece of javascript code that sets the url in the - * special hidden field and submits the form. - * - * Warning: This code should only be called in the rendering phase for form - * components inside the form because it uses the css/javascript id of the - * form which can be stored in the markup. - * - * @param url - * The interface url that has to be stored in the hidden field - * and submitted - * @return The javascript code that submits the form. - */ - public final CharSequence getJsForInterfaceUrl(CharSequence url) - { - return new AppendingStringBuffer("document.getElementById('").append(getHiddenFieldId()) - .append("').value='").append(url).append("';document.getElementById('").append( - getJavascriptId()).append("').submit();"); - } - - /** - * - */ - class FormDispatchRequest extends Request - { - private final Request realRequest; - - private final String url; - - private final ValueMap params = new ValueMap(); - - /** - * Construct. - * - * @param realRequest - * @param url - */ - public FormDispatchRequest(final Request realRequest, final String url) - { - this.realRequest = realRequest; - this.url = realRequest.decodeURL(url); - - String queryString = this.url.substring(this.url.indexOf("?") + 1); - RequestUtils.decodeParameters(queryString, params); - } - - /** - * @see org.apache.wicket.Request#getLocale() - */ - public Locale getLocale() - { - return realRequest.getLocale(); - } - - /** - * @see org.apache.wicket.Request#getParameter(java.lang.String) - */ - public String getParameter(String key) - { - return (String)params.get(key); - } - - /** - * @see org.apache.wicket.Request#getParameterMap() - */ - public Map getParameterMap() - { - return params; - } - - /** - * @see org.apache.wicket.Request#getParameters(java.lang.String) - */ - public String[] getParameters(String key) - { - String param = (String)params.get(key); - if (param != null) - { - return new String[] { param }; - } - return new String[0]; - } - - /** - * @see org.apache.wicket.Request#getPath() - */ - public String getPath() - { - return realRequest.getPath(); - } - - /** - * @see org.apache.wicket.Request#getURL() - */ - public String getURL() - { - return url; - } - - public String getRelativePathPrefixToContextRoot() - { - return realRequest.getRelativePathPrefixToContextRoot(); - } - - public String getRelativePathPrefixToWicketHandler() - { - return realRequest.getRelativePathPrefixToWicketHandler(); - } - } - - /** - * Returns the prefix used when building validator keys. This allows a form - * to use a separate "set" of keys. For example if prefix "short" is - * returned, validator key short.Required will be tried instead of - * Required key. - * <p> - * This can be useful when different designs are used for a form. In a form - * where error messages are displayed next to their respective form - * components as opposed to at the top of the form, the ${label} attribute - * is of little use and only causes redundant information to appear in the - * message. Forms like these can return the "short" (or any other string) - * validator prefix and declare key: short.Required=required to - * override the longer message which is usually declared like this: - * Required=${label} is a required field - * <p> - * Returned prefix will be used for all form components. The prefix can also - * be overridden on form component level by overriding - * [EMAIL PROTECTED] FormComponent#getValidatorKeyPrefix()} - * - * @return prefix prepended to validator keys - */ - public String getValidatorKeyPrefix() - { - return null; - } - - /** - * Adds a form validator to the form. - * - * @param validator - * validator - * @throws IllegalArgumentException - * if validator is null - * @see IFormValidator - * @see IValidatorAddListener - */ - public void add(IFormValidator validator) - { - if (validator == null) - { - throw new IllegalArgumentException("validator argument cannot be null"); - } - - // add the validator - formValidators_add(validator); - - // see whether the validator listens for add events - if (validator instanceof IValidatorAddListener) - { - ((IValidatorAddListener)validator).onAdded(this); - } - } - - /** - * @param validator - * The form validator to add to the formValidators Object (which - * may be an array of IFormValidators or a single instance, for - * efficiency) - */ - private void formValidators_add(final IFormValidator validator) - { - if (this.formValidators == null) - { - this.formValidators = validator; - } - else - { - // Get current list size - final int size = formValidators_size(); - - // Create array that holds size + 1 elements - final IFormValidator[] validators = new IFormValidator[size + 1]; - - // Loop through existing validators copying them - for (int i = 0; i < size; i++) - { - validators[i] = formValidators_get(i); - } - - // Add new validator to the end - validators[size] = validator; - - // Save new validator list - this.formValidators = validators; - } - } - - /** - * Gets form validator from formValidators Object (which may be an array of - * IFormValidators or a single instance, for efficiency) at the given index - * - * @param index - * The index of the validator to get - * @return The form validator - */ - private IFormValidator formValidators_get(int index) - { - if (this.formValidators == null) - { - throw new IndexOutOfBoundsException(); - } - if (this.formValidators instanceof IFormValidator[]) - { - return ((IFormValidator[])formValidators)[index]; - } - return (IFormValidator)formValidators; - } - - /** - * @return The number of form validators in the formValidators Object (which - * may be an array of IFormValidators or a single instance, for - * efficiency) - */ - private int formValidators_size() - { - if (this.formValidators == null) - { - return 0; - } - if (this.formValidators instanceof IFormValidator[]) - { - return ((IFormValidator[])formValidators).length; - } - return 1; - } - - /** - * /** Registers an error feedback message for this component - * - * @param error - * error message - * @param args - * argument replacement map for ${key} variables - */ - public final void error(String error, Map args) - { - error(new MapVariableInterpolator(error, args).toString()); - } - - /** - * Returns whether the form is a root form, which means that there's no - * other form in it's parent hierarchy. - * - * @return true if form is a root form, false otherwise - */ - public boolean isRootForm() - { - return findParent(Form.class) == null; - } - - /** - * Returns the root form or this, if this is the root form. - * - * @return root form or this form - */ - public Form getRootForm() - { - Form form; - Form parent = this; - do - { - form = parent; - parent = (Form)form.findParent(Form.class); - } - while (parent != null); - - return form; } }