/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.markup.html.form;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.Request;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.border.Border;
import org.apache.wicket.markup.html.form.persistence.CookieValuePersister;
import org.apache.wicket.markup.html.form.persistence.IValuePersister;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.markup.html.form.validation.IFormValidator;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.request.IRequestCycleProcessor;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.component.listener.IListenerInterfaceRequestTarget;
import org.apache.wicket.settings.IApplicationSettings;
import org.apache.wicket.util.lang.Bytes;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.interpolator.MapVariableInterpolator;
import org.apache.wicket.util.upload.FileUploadException;
import org.apache.wicket.util.upload.FileUploadBase.SizeLimitExceededException;
import org.apache.wicket.validation.IValidatorAddListener;
import org.apache.wicket.version.undo.Change;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for forms. To implement a form, subclass this class, add FormComponents (such as
* CheckBoxes, ListChoices or TextFields) to the form. You can nest multiple
* IFormSubmittingComponents if you want to vary submit behavior. However, it is not necessary to
* use any of Wicket's classes (such as Button or SubmitLink), just putting e.g. <input
* type="submit" value="go"> suffices.
* <p>
* By default, the processing of a form works like this:
* <li> The submitting component is looked up. An submitting IFormSubmittingComponent (such as a
* button) is nested in this form (is a child component) and was clicked by the user. If an
* IFormSubmittingComponent was found, and it has the defaultFormProcessing field set to false
* (default is true), it's onSubmit method will be called right away, thus no validition is done,
* and things like updating form component models that would normally be done are skipped. In that
* respect, nesting an IFormSubmittingComponent with the defaultFormProcessing field set to false
* has the same effect as nesting a normal link. If you want you can call validate() to execute form
* validation, hasError() to find out whether validate() resulted in validation errors, and
* updateFormComponentModels() to update the models of nested form components. </li>
* <li> When no submitting IFormSubmittingComponent with defaultFormProcessing set to false was
* found, this form is processed (method process()). Now, two possible paths exist:
* <ul>
* <li> Form validation failed. All nested form components will be marked invalid, and onError() is
* called to allow clients to provide custom error handling code. </li>
* <li> Form validation succeeded. The nested components will be asked to update their models and
* persist their data is applicable. After that, method delegateSubmit with optionally the
* submitting IFormSubmittingComponent is called. The default when there is a submitting
* IFormSubmittingComponent is to first call onSubmit on that Component, and after that call
* onSubmit on this form. Clients may override delegateSubmit if they want different behavior. </li>
* </ul>
* </li>
* </li>
* </p>
*
* Form for handling (file) uploads with multipart requests is supported by calling
* setMultiPart(true) ( although wicket will try to automatically detect this for you ). Use this
* with {@link org.apache.wicket.markup.html.form.upload.FileUploadField} components. You can attach
* multiple FileUploadField components for multiple file uploads.
* <p>
* In case of an upload error two resource keys are available to specify error messages:
* uploadTooLarge and uploadFailed
*
* ie in [page].properties
*
* [form-id].uploadTooLarge=You have uploaded a file that is over the allowed limit of 2Mb
*
* <p>
* If you want to have multiple IFormSubmittingComponents which submit the same form, simply put two
* or more IFormSubmittingComponents somewhere in the hierarchy of components that are children of
* the form.
* </p>
* <p>
* To get form components to persist their values for users via cookies, simply call
* setPersistent(true) on each component.
* </p>
* <p>
* Forms can be nested. You can put a form in another form. Since HTML doesn't allow nested
* <form> tags, the inner forms will be rendered using the <div> tag. You have to submit
* the inner forms using explicit components (like Button or SubmitLink), you can't rely on implicit
* submit behavior (by using just <input type="submit"> that is not attached to a component).
* </p>
* <p>
* When a nested form is submitted, the user entered values in outer (parent) forms are preserved
* and only the fields in the submitted form are validated. </b>
*
* @author Jonathan Locke
* @author Juergen Donnerstag
* @author Eelco Hillenius
* @author Cameron Braid
* @author Johan Compagner
* @author Igor Vaynberg (ivaynberg)
* @author David Leangen
*
* @param <T>
* The model object type
*/
public class Form<T> extends WebMarkupContainer<T> implements IFormSubmitListener
{
/**
* Visitor used for validation
*
* @author Igor Vaynberg (ivaynberg)
*/
public static abstract class ValidationVisitor implements FormComponent.IVisitor
{
/**
* @see org.apache.wicket.markup.html.form.FormComponent.IVisitor#formComponent(org.apache.wicket.markup.html.form.IFormVisitorParticipant)
*/
public Object formComponent(IFormVisitorParticipant component)
{
if (component instanceof FormComponent)
{
FormComponent< ? > formComponent = (FormComponent< ? >)component;
Form< ? > form = formComponent.getForm();
if (!form.isEnabled() || !form.isEnableAllowed() || !form.isVisibleInHierarchy())
{
// do not validate formComponent or any of formComponent's children
return Component.IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
if (formComponent.isVisibleInHierarchy() && formComponent.isValid() &&
formComponent.isEnabled() && formComponent.isEnableAllowed())
{
validate(formComponent);
}
}
if (component.processChildren())
{
return Component.IVisitor.CONTINUE_TRAVERSAL;
}
else
{
return Component.IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
}
/**
* Callback that should be used to validate form component
*
* @param formComponent
*/
public abstract void validate(FormComponent< ? > formComponent);
}
/**
*
*/
class FormDispatchRequest extends Request
{
private final Map<String, String[]> params = new HashMap<String, String[]>();
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.decodeUrlParameters(queryString, params);
}
/**
* @see org.apache.wicket.Request#getLocale()
*/
@Override
public Locale getLocale()
{
return realRequest.getLocale();
}
/**
* @see org.apache.wicket.Request#getParameter(java.lang.String)
*/
@Override
public String getParameter(String key)
{
String p[] = params.get(key);
return p != null && p.length > 0 ? p[0] : null;
}
/**
* @see org.apache.wicket.Request#getParameterMap()
*/
@Override
public Map<String, String[]> getParameterMap()
{
return params;
}
/**
* @see org.apache.wicket.Request#getParameters(java.lang.String)
*/
@Override
public String[] getParameters(String key)
{
String[] param = params.get(key);
if (param != null)
{
return param;
}
return new String[0];
}
/**
* @see org.apache.wicket.Request#getPath()
*/
@Override
public String getPath()
{
return realRequest.getPath();
}
@Override
public String getRelativePathPrefixToContextRoot()
{
return realRequest.getRelativePathPrefixToContextRoot();
}
@Override
public String getRelativePathPrefixToWicketHandler()
{
return realRequest.getRelativePathPrefixToWicketHandler();
}
/**
* @see org.apache.wicket.Request#getURL()
*/
@Override
public String getURL()
{
return url;
}
@Override
public String getQueryString()
{
return realRequest.getQueryString();
}
}
/**
* 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;
/** Log. */
private static final Logger log = LoggerFactory.getLogger(Form.class);
private static final long serialVersionUID = 1L;
private static final String UPLOAD_FAILED_RESOURCE_KEY = "uploadFailed";
private static final String UPLOAD_TOO_LARGE_RESOURCE_KEY = "uploadTooLarge";
/**
* Any default IFormSubmittingComponent. If set, a hidden submit component will be rendered
* right after the form tag, so that when users press enter in a textfield, this submit
* component's action will be selected. If no default IFormSubmittingComponent is set, nothing
* additional is rendered.
* <p>
* WARNING: note that this is a best effort only. Unfortunately having a 'default'
* IFormSubmittingComponent in a form is ill defined in the standards, and of course IE has it's
* own way of doing things.
* </p>
*/
private IFormSubmittingComponent defaultSubmittingComponent;
/** multi-validators assigned to this form */
private Object formValidators = null;
private String javascriptId;
/**
* Maximum size of an upload in bytes. If null, the setting
* {@link IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*/
private Bytes maxSize = null;
/** True if the form has enctype of multipart/form-data */
private boolean multiPart = false;
/**
* Constructs a form with no validation.
*
* @param id
* See Component
*/
public Form(final String id)
{
super(id);
setOutputMarkupId(true);
}
/**
* @param id
* See Component
* @param model
* See Component
* @see org.apache.wicket.Component#Component(String, IModel)
*/
public Form(final String id, IModel<T> model)
{
super(id, model);
setOutputMarkupId(true);
}
/**
* 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("Argument `validator` 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);
}
}
/**
* Removes a form validator from the form.
*
* @param validator
* validator
* @throws IllegalArgumentException
* if validator is null
* @see IFormValidator
*/
public void remove(IFormValidator validator)
{
if (validator == null)
{
throw new IllegalArgumentException("Argument `validator` cannot be null");
}
IFormValidator removed = formValidators_remove(validator);
if (removed == null)
{
throw new IllegalStateException(
"Tried to remove form validator that was not previously added. "
+ "Make sure your validator's equals() implementation is sufficient");
}
addStateChange(new FormValidatorRemovedChange(removed));
}
private final int formValidators_indexOf(IFormValidator validator)
{
if (formValidators != null)
{
if (formValidators instanceof IFormValidator)
{
final IFormValidator v = (IFormValidator)formValidators;
if (v == validator || v.equals(validator))
{
return 0;
}
}
else
{
final IFormValidator[] validators = (IFormValidator[])formValidators;
for (int i = 0; i < validators.length; i++)
{
final IFormValidator v = validators[i];
if (v == validator || v.equals(validator))
{
return i;
}
}
}
}
return -1;
}
private final IFormValidator formValidators_remove(IFormValidator validator)
{
int index = formValidators_indexOf(validator);
if (index != -1)
{
return formValidators_remove(index);
}
return null;
}
private final IFormValidator formValidators_remove(int index)
{
if (formValidators instanceof IFormValidator)
{
if (index == 0)
{
final IFormValidator removed = (IFormValidator)formValidators;
formValidators = null;
return removed;
}
else
{
throw new IndexOutOfBoundsException();
}
}
else
{
final IFormValidator[] validators = (IFormValidator[])formValidators;
final IFormValidator removed = validators[index];
// check if we can collapse array of 1 element into a single object
if (validators.length == 2)
{
formValidators = validators[1 - index];
}
else
{
IFormValidator[] newValidators = new IFormValidator[validators.length - 1];
int j = 0;
for (int i = 0; i < validators.length; i++)
{
if (i != index)
{
newValidators[j++] = validators[i];
}
}
formValidators = newValidators;
}
return removed;
}
}
/**
* Clears the input from the form's nested children of type {@link 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()
{
@Override
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 IFormSubmittingComponent which submitted this form.
*
* @return The component which submitted this form, or null if the processing was not triggered
* by a registered IFormSubmittingComponent
*/
public final IFormSubmittingComponent findSubmittingButton()
{
IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)getPage().visitChildren(
IFormSubmittingComponent.class, new IVisitor<Component< ? >>()
{
public Object component(final Component< ? > component)
{
// Get submitting component
final IFormSubmittingComponent submittingComponent = (IFormSubmittingComponent)component;
// Check for component-name or component-name.x request string
if (submittingComponent.getForm() != null &&
submittingComponent.getForm().getRootForm() == Form.this &&
(getRequest().getParameter(submittingComponent.getInputName()) != null || getRequest().getParameter(
submittingComponent.getInputName() + ".x") != null))
{
if (!component.isVisible())
{
throw new WicketRuntimeException("Submit Button " +
submittingComponent.getInputName() + " (path=" +
component.getPageRelativePath() + ") is not visible");
}
return submittingComponent;
}
return CONTINUE_TRAVERSAL;
}
});
return submittingComponent;
}
/**
* Gets the default IFormSubmittingComponent. If set (not null), a hidden submit component will
* be rendered right after the form tag, so that when users press enter in a textfield, this
* submit component's action will be selected. If no default component is set (it is null),
* nothing additional is rendered.
* <p>
* WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a
* form is ill defined in the standards, and of course IE has it's own way of doing things.
* </p>
* There can be only one default submit component per form hierarchy. So if you want to get the
* default component on a nested form, it will actually delegate the call to root form. </b>
*
* @return The submit component to set as the default IFormSubmittingComponent, or null when you
* want to 'unset' any previously set default IFormSubmittingComponent
*/
public final IFormSubmittingComponent getDefaultButton()
{
if (isRootForm())
{
return defaultSubmittingComponent;
}
else
{
return getRootForm().getDefaultButton();
}
}
/**
* 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)
{
Form< ? > root = getRootForm();
return new AppendingStringBuffer("document.getElementById('").append(
root.getHiddenFieldId()).append("').value='").append(url).append(
"';document.getElementById('").append(root.getJavascriptId()).append("').submit();");
}
/**
* Gets the maximum size for uploads. If null, the setting
* {@link IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*
* @return the maximum size
*/
public Bytes getMaxSize()
{
if (maxSize == null)
{
return getApplication().getApplicationSettings().getDefaultMaximumUploadSize();
}
return maxSize;
}
/**
* 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 {@link 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()
*/
@Override
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()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
// Component must implement persister interface and
// persistence for that component must be enabled.
// Else ignore the persisted value. It'll be deleted
// once the user submits the Form containing that FormComponent.
// Note: if that is true, values may remain persisted longer
// than really necessary
if (formComponent.isVisibleInHierarchy() && formComponent.isPersistent())
{
// The persister
final IValuePersister persister = getValuePersister();
// Retrieve persisted value
persister.load(formComponent);
}
}
});
}
/**
* THIS METHOD IS NOT PART OF THE WICKET API. DO NOT ATTEMPT TO OVERRIDE OR CALL IT.
*
* Handles form submissions.
*
* @see Form#validate()
*/
public final void onFormSubmitted()
{
markFormsSubmitted();
if (handleMultiPart())
{
// Tells FormComponents that a new user input has come
inputChanged();
String url = getRequest().getParameter(getHiddenFieldId());
if (!Strings.isEmpty(url))
{
dispatchEvent(getPage(), url);
}
else
{
// First, see if the processing was triggered by a Wicket IFormSubmittingComponent
final IFormSubmittingComponent submittingComponent = findSubmittingButton();
// When processing was triggered by a Wicket IFormSubmittingComponent and that
// component indicates it wants to be called immediately
// (without processing), call IFormSubmittingComponent.onSubmit() right away.
if (submittingComponent != null && !submittingComponent.getDefaultFormProcessing())
{
submittingComponent.onSubmit();
}
else
{
// this is the root form
Form< ? > formToProcess = this;
// find out whether it was a nested form that was submitted
if (submittingComponent != null)
{
formToProcess = submittingComponent.getForm();
}
// process the form for this request
if (formToProcess.process())
{
// let clients handle further processing
delegateSubmit(submittingComponent);
}
}
}
}
// If multi part did fail check if an error is registered and call
// onError
else if (hasError())
{
callOnError();
}
}
/**
* 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
*/
public boolean process()
{
if (!isEnabled() || !isEnableAllowed() || !isVisibleInHierarchy())
{
// since process() can be called outside of the default form workflow, an additional
// check is needed
return false;
}
// run validation
validate();
// If a validation error occurred
if (hasError())
{
// mark all children as invalid
markFormComponentsInvalid();
// let subclass handle error
callOnError();
// Form has an error
return false;
}
else
{
// mark all children 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;
}
}
/**
* Calls onError on this {@link Form} and any enabled and visible nested form, if the respective
* {@link Form} actually has errors.
*/
private void callOnError()
{
onError();
// call onError on nested forms
visitChildren(Form.class, new IVisitor<Component< ? >>()
{
public Object component(Component< ? > component)
{
final Form< ? > form = (Form< ? >)component;
if (!form.isEnabled() || !form.isEnableAllowed() || !form.isVisibleInHierarchy())
{
return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
if (form.hasError())
{
form.onError();
}
return IVisitor.CONTINUE_TRAVERSAL;
}
});
}
/**
* Sets FLAG_SUBMITTED to true on this form and every enabled nested form.
*/
private void markFormsSubmitted()
{
setFlag(FLAG_SUBMITTED, true);
visitChildren(Form.class, new IVisitor<Component< ? >>()
{
public Object component(Component< ? > component)
{
Form< ? > form = (Form< ? >)component;
if (form.isEnabled() && form.isEnableAllowed() && isVisibleInHierarchy())
{
form.setFlag(FLAG_SUBMITTED, true);
return IVisitor.CONTINUE_TRAVERSAL;
}
return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
});
}
/**
* Removes already persisted data for all FormComponent children and disable persistence for the
* same components.
*
* @see Page#removePersistedFormData(Class, boolean)
*
* @param disablePersistence
* if true, disable persistence for all FormComponents on that page. If false, it
* will remain unchanged.
*/
public void removePersistentFormComponentValues(final boolean disablePersistence)
{
// The persistence manager responsible to persist and retrieve
// FormComponent data
final IValuePersister persister = getValuePersister();
// Search for FormComponents like TextField etc.
visitFormComponentsPostOrder(new FormComponent.AbstractVisitor()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
if (formComponent.isVisibleInHierarchy())
{
// remove the FormComponent's persisted data
persister.clear(formComponent);
// Disable persistence if requested. Leave unchanged
// otherwise.
if (formComponent.isPersistent() && disablePersistence)
{
formComponent.setPersistent(false);
}
}
}
});
}
/**
* Sets the default IFormSubmittingComponent. If set (not null), a hidden submit component will
* be rendered right after the form tag, so that when users press enter in a textfield, this
* submit component's action will be selected. If no default component is set (so unset by
* calling this method with null), nothing additional is rendered.
* <p>
* WARNING: note that this is a best effort only. Unfortunately having a 'default' button in a
* form is ill defined in the standards, and of course IE has it's own way of doing things.
* </p>
* There can be only one default button per form hierarchy. So if you set default button on a
* nested form, it will actually delegate the call to root form. </b>
*
* @param submittingComponent
* The component to set as the default submitting component, or null when you want to
* 'unset' any previously set default component
*/
public final void setDefaultButton(IFormSubmittingComponent submittingComponent)
{
if (isRootForm())
{
defaultSubmittingComponent = submittingComponent;
}
else
{
getRootForm().setDefaultButton(submittingComponent);
}
}
/**
* Sets the maximum size for uploads. If null, the setting
* {@link IApplicationSettings#getDefaultMaximumUploadSize()} is used.
*
* @param maxSize
* The maximum size
*/
public void setMaxSize(final Bytes maxSize)
{
this.maxSize = maxSize;
}
/**
* Set to true to use enctype='multipart/form-data', and to process file uploads by default
* multiPart = false
*
* @param multiPart
* whether this form should behave as a multipart form
*/
public void setMultiPart(boolean multiPart)
{
this.multiPart = multiPart;
}
/**
* @see org.apache.wicket.Component#setVersioned(boolean)
*/
@Override
public final Component<T> setVersioned(final boolean isVersioned)
{
super.setVersioned(isVersioned);
// Search for FormComponents like TextField etc.
visitFormComponents(new FormComponent.AbstractVisitor()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
formComponent.setVersioned(isVersioned);
}
});
return this;
}
/**
* Convenient and typesafe way to visit all the form components on a form.
*
* @param visitor
* The visitor interface to call
*/
public final void visitFormComponents(final FormComponent.IVisitor visitor)
{
visitChildren(FormComponent.class, new IVisitor<Component< ? >>()
{
public Object component(final Component< ? > component)
{
visitor.formComponent((FormComponent< ? >)component);
return CONTINUE_TRAVERSAL;
}
});
visitChildrenInContainingBorder(visitor);
}
/**
* 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)
{
FormComponent.visitFormComponentsPostOrder(this, visitor);
visitChildrenInContainingBorder(visitor);
}
/**
* TODO Post 1.2 General: Maybe we should re-think how Borders are implemented, because there
* are just too many exceptions in the code base because of borders. This time it is to solve
* the problem tested in BoxBorderTestPage_3 where the Form is defined in the box border and the
* FormComponents are in the "body". Thus, the formComponents are not children of the form. They
* are rather children of the border, as the Form itself.
*
* @param visitor
* The {@link Component}.{@link IVisitor} used to visit the children.
*/
private void visitChildrenInContainingBorder(final FormComponent.IVisitor visitor)
{
if (getParent() instanceof Border)
{
MarkupContainer< ? > border = getParent();
Iterator<Component< ? >> iter = border.iterator();
while (iter.hasNext())
{
Component< ? > child = iter.next();
if (child instanceof FormComponent)
{
visitor.formComponent((FormComponent< ? >)child);
}
}
}
}
/**
* 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<Component< ? >>()
{
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;
}
/**
* Method for dispatching/calling a interface on a page from the given url. Used by
* {@link 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 IListenerInterfaceRequestTarget)
{
IListenerInterfaceRequestTarget interfaceTarget = ((IListenerInterfaceRequestTarget)rt);
interfaceTarget.getRequestListenerInterface().invoke(page, interfaceTarget.getTarget());
}
else
{
throw new WicketRuntimeException(
"Attempt to access unknown request listener interface " +
requestParameters.getInterfaceName());
}
}
/**
* @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 (formValidators == null)
{
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
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 (formValidators == null)
{
throw new IndexOutOfBoundsException();
}
if (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 (formValidators == null)
{
return 0;
}
if (formValidators instanceof IFormValidator[])
{
return ((IFormValidator[])formValidators).length;
}
return 1;
}
/**
* 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()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
if (formComponent.isVisibleInHierarchy())
{
formComponent.inputChanged();
}
}
});
}
/**
* 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()
{
@Override
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);
}
}
}
});
}
}
/**
* If a default IFormSubmittingComponent 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 component. 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 appendDefaultButtonField(final MarkupStream markupStream,
final ComponentTag openTag)
{
AppendingStringBuffer buffer = new AppendingStringBuffer();
// div that is not visible (but not display:none either)
buffer.append("<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">");
// add an empty textfield (otherwise IE doesn't work)
buffer.append("<input type=\"text\" autocomplete=\"false\"/>");
// add the submitting component
final Component< ? > submittingComponent = (Component< ? >)defaultSubmittingComponent;
buffer.append("<input type=\"submit\" name=\"");
buffer.append(defaultSubmittingComponent.getInputName());
buffer.append("\" onclick=\" var b=Wicket.$('");
buffer.append(submittingComponent.getMarkupId());
buffer.append("'); if (typeof(b.onclick) != 'undefined') { var r = b.onclick.bind(b)(); if (r != false) b.click(); } else { b.click(); }; return false;\" ");
buffer.append(" />");
// close div
buffer.append("</div>");
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
* IFormSubmittingComponent of this form. If that is the case, that component's onSubmit is
* called first.
* </p>
* <p>
* Regardless of whether a submitting component was found, the form's onSubmit method is called
* next.
* </p>
*
* @param submittingComponent
* the component 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 submittingComponent)
{
// when the given submitting component is not null, it means that it was the
// submitting component
Form< ? > formToProcess = this;
if (submittingComponent != null)
{
// use the form which the submittingComponent has submitted for further processing
formToProcess = submittingComponent.getForm();
submittingComponent.onSubmit();
}
// Model was successfully updated with valid data
formToProcess.onSubmit();
// call onSubmit on nested forms
formToProcess.visitChildren(Form.class, new IVisitor<Form< ? >>()
{
public Object component(Form< ? > component)
{
Form< ? > form = component;
if (form.isEnabled() && form.isEnableAllowed() && form.isVisibleInHierarchy())
{
form.onSubmit();
return IVisitor.CONTINUE_TRAVERSAL;
}
return IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
});
}
/**
* 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 getInputNamePrefix() + getJavascriptId() + "_hf_0";
}
/**
* Returns the javascript/css id of this form that will be used to generated the id="xxx"
* attribute.
*
* @return The javascript/css id of this form.
* @deprecated use {@link #getMarkupId()}
*/
@Deprecated
protected final String getJavascriptId()
{
return getMarkupId();
}
/**
* Gets the HTTP submit method that will appear in form markup. If no method is specified in the
* template, "post" is the default. Note that the markup-declared HTTP method may not correspond
* to the one actually used to submit the form; in an Ajax submit, for example, JavaScript event
* handlers may submit the form with a "get" even when the form method is declared as "post."
* Therefore this method should not be considered a guarantee of the HTTP method used, but a
* value for the markup only. Override if you have a requirement to alter this behavior.
*
* @return the submit method specified in markup.
*/
protected String getMethod()
{
String method = getMarkupAttributes().getString("method");
return (method != null) ? method : METHOD_POST;
}
/**
*
* @see org.apache.wicket.Component#getStatelessHint()
*/
@Override
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 {@link FileUploadField}s on this form
*
* @return false if form is multipart and upload failed
*/
protected boolean handleMultiPart()
{
if (multiPart && !((WebRequest)getRequest()).isAjax())
{
// Change the request to a multipart web request so parameters are
// parsed out correctly
try
{
final WebRequest multipartWebRequest = ((WebRequest)getRequest()).newMultipartWebRequest(getMaxSize());
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<String, Object> model = new HashMap<String, Object>();
model.put("exception", e);
model.put("maxSize", getMaxSize());
if (e instanceof SizeLimitExceededException)
{
// Resource key should be <form-id>.uploadTooLarge to
// override default message
final String defaultValue = "Upload must be less than " + getMaxSize();
String msg = getString(getId() + "." + UPLOAD_TOO_LARGE_RESOURCE_KEY,
Model.valueOf(model), defaultValue);
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.warn(msg, e);
}
// don't process the form if there is a FileUploadException
return false;
}
}
return true;
}
/**
* @see org.apache.wicket.Component#internalOnModelChanged()
*/
@Override
protected void internalOnModelChanged()
{
// Visit all the form components and validate each
visitFormComponentsPostOrder(new FormComponent.AbstractVisitor()
{
@Override
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()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
if (formComponent.isVisibleInHierarchy())
{
formComponent.invalid();
}
}
});
}
/**
* Mark each form component on this form and on nested forms valid.
*/
protected final void markFormComponentsValid()
{
internalMarkFormComponentsValid();
markNestedFormComponentsValid();
}
/**
* Mark each form component on nested form valid.
*/
private void markNestedFormComponentsValid()
{
visitChildren(Form.class, new IVisitor<Form< ? >>()
{
public Object component(Form< ? > component)
{
Form< ? > form = component;
if (form.isEnableAllowed() && form.isEnabled() && form.isVisibleInHierarchy())
{
form.internalMarkFormComponentsValid();
return CONTINUE_TRAVERSAL;
}
return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
});
}
/**
* Mark each form component on this form valid.
*/
private void internalMarkFormComponentsValid()
{
// call valid methods of all nested form components
visitFormComponentsPostOrder(new FormComponent.AbstractVisitor()
{
@Override
public void onFormComponent(final FormComponent< ? > formComponent)
{
if (formComponent.getForm() == Form.this && formComponent.isVisibleInHierarchy())
{
formComponent.valid();
}
}
});
}
/**
* @see org.apache.wicket.Component#onComponentTag(ComponentTag)
*/
@Override
protected void onComponentTag(final ComponentTag tag)
{
super.onComponentTag(tag);
checkComponentTag(tag, "form");
if (isRootForm())
{
String method = getMethod().toLowerCase();
tag.put("method", method);
String url = urlFor(IFormSubmitListener.INTERFACE).toString();
if (encodeUrlInHiddenFields())
{
int i = url.indexOf('?');
String action = (i > -1) ? url.substring(0, i) : "";
tag.put("action", action);
// alternatively, we could just put an empty string here, so
// that mounted paths stay in good order. I decided against this
// as I'm not sure whether that could have side effects with
// other encoders
}
else
{
tag.put("action", Strings.replaceAll(url, "&", "&"));
}
if (multiPart)
{
tag.put("enctype", "multipart/form-data");
}
else
{
// sanity check
String enctype = (String)tag.getAttributes().get("enctype");
if ("multipart/form-data".equalsIgnoreCase(enctype))
{
// though not set explicitly in Java, this is a multipart
// form
setMultiPart(true);
}
}
}
else
{
tag.setName("div");
tag.remove("method");
tag.remove("action");
tag.remove("enctype");
}
}
/**
*
* @return
*/
protected boolean encodeUrlInHiddenFields()
{
String method = getMethod().toLowerCase();
return method.equals("get");
}
/**
* 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
*/
@Override
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
if (isRootForm())
{
// 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("\" />");
// if it's a get, did put the parameters in the action attribute,
// and have to write the url parameters as hidden fields
if (encodeUrlInHiddenFields())
{
String url = urlFor(IFormSubmitListener.INTERFACE).toString();
int i = url.indexOf('?');
String[] params = ((i > -1) ? url.substring(i + 1) : url).split("&");
writeParamsAsHiddenFields(params, buffer);
}
buffer.append("</div>");
getResponse().write(buffer);
// if a default submitting component was set, handle the rendering of that
if (defaultSubmittingComponent instanceof Component)
{
final Component< ? > submittingComponent = (Component< ? >)defaultSubmittingComponent;
if (submittingComponent.isVisibleInHierarchy() && submittingComponent.isEnabled())
{
appendDefaultButtonField(markupStream, openTag);
}
}
}
// do the rest of the processing
super.onComponentTagBody(markupStream, openTag);
}
/**
*
* @param params
* @param buffer
*/
protected void writeParamsAsHiddenFields(String[] params, AppendingStringBuffer buffer)
{
for (int j = 0; j < params.length; j++)
{
String[] pair = params[j].split("=");
buffer.append("<input type=\"hidden\" name=\"")
.append(pair[0])
.append("\" value=\"")
.append(pair.length > 1 ? pair[1] : "")
.append("\" />");
}
}
/**
* @see org.apache.wicket.Component#onDetach()
*/
@Override
protected void onDetach()
{
super.internalOnDetach();
setFlag(FLAG_SUBMITTED, false);
super.onDetach();
}
/**
* 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)
*/
@Override
protected void onRender(final MarkupStream markupStream)
{
// Force multi-part on if any child form component is multi-part
visitFormComponents(new FormComponent.AbstractVisitor()
{
@Override
public void onFormComponent(FormComponent< ? > formComponent)
{
if (formComponent.isVisible() && formComponent.isMultiPart())
{
setMultiPart(true);
}
}
});
super.onRender(markupStream);
}
/**
* Implemented by subclasses to deal with form submits.
*/
protected void onSubmit()
{
}
/**
* Update the model of all components on this form and nested forms using the fields that were
* sent with the current request. This method only updates models when the Form.validate() is
* called first that takes care of the conversion for the FormComponents.
*
* Normally this method will not be called when a validation error occurs in one of the form
* components.
*
* @see org.apache.wicket.markup.html.form.FormComponent#updateModel()
*/
protected final void updateFormComponentModels()
{
internalUpdateFormComponentModels();
updateNestedFormComponentModels();
}
/**
* Update the model of all components on nested forms.
*
* @see #updateFormComponentModels()
*/
private final void updateNestedFormComponentModels()
{
visitChildren(Form.class, new IVisitor<Form< ? >>()
{
public Object component(Form< ? > form)
{
if (form.isEnabled() && form.isEnableAllowed() && form.isVisibleInHierarchy())
{
form.internalUpdateFormComponentModels();
return CONTINUE_TRAVERSAL;
}
return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
});
}
/**
* Update the model of all components on this form.
*
* @see #updateFormComponentModels()
*/
private void internalUpdateFormComponentModels()
{
visitFormComponentsPostOrder(new ValidationVisitor()
{
@Override
public void validate(FormComponent< ? > formComponent)
{
Form< ? > form = formComponent.getForm();
if (form == Form.this)
{
// Potentially update the model
formComponent.updateModel();
}
}
});
}
/**
* 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.
* <p>
* NOTE: in most cases, custom validations on the form can be achieved using an IFormValidator
* that can be added using addValidator().
* </p>
*/
protected void validate()
{
if (isEnabled() && isEnableAllowed() && isVisibleInHierarchy())
{
// since this method can be called directly by users, this additional check is needed
validateComponents();
validateFormValidators();
validateNestedForms();
}
}
/**
* Triggers type conversion on form components
*/
protected final void validateComponents()
{
visitFormComponentsPostOrder(new ValidationVisitor()
{
@Override
public void validate(final FormComponent< ? > formComponent)
{
final Form< ? > form = formComponent.getForm();
if (form == Form.this && form.isEnabled() && form.isEnableAllowed() &&
form.isVisibleInHierarchy())
{
formComponent.validate();
}
}
});
}
/**
* Checks if the specified form component visible and is attached to a page
*
* @param fc
* form component
*
* @return true if the form component and all its parents are visible and there component is in
* page's hierarchy
*/
private boolean isFormComponentVisibleInPage(FormComponent< ? > fc)
{
if (fc == null)
{
throw new IllegalArgumentException("Argument `fc` cannot be null");
}
return fc.isVisibleInHierarchy();
}
/**
* Validates form with the given form validator
*
* @param validator
*/
protected final void validateFormValidator(final IFormValidator validator)
{
if (validator == null)
{
throw new IllegalArgumentException("Argument [[validator]] cannot be null");
}
final FormComponent< ? >[] dependents = validator.getDependentFormComponents();
boolean validate = true;
if (dependents != null)
{
for (int j = 0; j < dependents.length; j++)
{
final FormComponent< ? > dependent = dependents[j];
// check if the dependent component is valid
if (!dependent.isValid())
{
validate = false;
break;
}
// check if the dependent component is visible and is attached to
// the page
else if (!isFormComponentVisibleInPage(dependent))
{
if (log.isWarnEnabled())
{
log.warn("IFormValidator in form `" +
getPageRelativePath() +
"` depends on a component that has been removed from the page or is no longer visible. " +
"Offending component id `" + dependent.getId() + "`.");
}
validate = false;
break;
}
}
}
if (validate)
{
validator.validate(this);
}
}
/**
* Triggers any added {@link IFormValidator}s.
*/
protected final void validateFormValidators()
{
final int count = formValidators_size();
for (int i = 0; i < count; i++)
{
validateFormValidator(formValidators_get(i));
}
}
/**
* Validates {@link FormComponent}s as well as {@link IFormValidator}s in nested {@link Form}s.
*
* @see #validate()
*/
private void validateNestedForms()
{
visitChildren(Form.class, new IVisitor<Form< ? >>()
{
public Object component(Form< ? > form)
{
if (form.isEnabled() && form.isEnableAllowed() && form.isVisibleInHierarchy())
{
form.validateComponents();
form.validateFormValidators();
return CONTINUE_TRAVERSAL;
}
return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
}
});
}
/**
* Change object to keep track of form validator removals
*
* @author Igor Vaynberg (ivaynberg at apache dot org)
*/
private class FormValidatorRemovedChange extends Change
{
private static final long serialVersionUID = 1L;
private final IFormValidator removed;
/**
* Construct.
*
* @param removed
*/
public FormValidatorRemovedChange(final IFormValidator removed)
{
super();
this.removed = removed;
}
@Override
public void undo()
{
add(removed);
}
}
/**
* Allows to customize input names of form components inside this form.
*
* @return String that well be used as prefix to form component input names
*/
protected String getInputNamePrefix()
{
return "";
}
}