Multi-action that implements common logic dealing with input forms. This class uses the Spring Web data binding code to do binding and validation.
Several action execution methods are provided:
- {@link #setupForm(RequestContext)} - Prepares the form object for display on a form,{@link #createFormObject(RequestContext) creating it} and an associated {@link Errors errors} if necessary, thencaching them in the configured {@link #getFormObjectScope() form object scope} and{@link #getFormErrorsScope() form errors scope}, respectively. Also {@link #registerPropertyEditors(PropertyEditorRegistry) installs} any custom property editors for formatting formobject field values. This action method will return the "success" event unless an exception is thrown.
- {@link #bindAndValidate(RequestContext)} - Binds all incoming request parameters to the form object and thenvalidates the form object using a {@link #setValidator(Validator) registered validator}. This action method will return the "success" event if there are no binding or validation errors, otherwise it will return the "error" event.
- {@link #bind(RequestContext)} - Binds all incoming request parameters to the form object. No additionalvalidation is performed. This action method will return the "success" event if there are no binding errors, otherwise it will return the "error" event.
- {@link #validate(RequestContext)} - Validates the form object using a registered validator. No data binding isperformed. This action method will return the "success" event if there are no validation errors, otherwise it will return the "error" event.
- {@link #resetForm(RequestContext)} - Resets the form by reloading the backing form object and reinstalling anycustom property editors. Returns "success" on completion, an exception is thrown when a failure occurs.
Since this is a multi-action a subclass could add any number of additional action execution methods, e.g. "setupReferenceData(RequestContext)", or "processSubmit(RequestContext)".
Using this action, it becomes very easy to implement form preparation and submission logic in your flow. One way to do this follows:
- Create a view state to display the form. In a render action of that state, invoke {@link #setupForm(RequestContext) setupForm} to prepare the new form for display.
- On a matching "submit" transition execute an action that invokes {@link #bindAndValidate(RequestContext) bindAndValidate} to bind incoming request parameters to the form object andvalidate the form object.
- If there are binding or validation errors, the transition will not be allowed and the view state will automatically be re-entered.
- If binding and validation are successful go to an action state called "processSubmit" (or any other appropriate name). This will invoke an action method called "processSubmit" you must provide on a subclass to process form submission, e.g. interacting with the business logic.
- If business processing is ok, continue to a view state to display the success view.
Here is an example implementation of such a form flow:
<view-state id="displayCriteria"> <on-render> <evaluate expression="formAction.setupForm"/> </on-render> <transition on="search" to="executeSearch"> <evaluate expression="formAction.bindAndValidate"/> </transition> </view-state>
When you need additional flexibility consider splitting the view state above acting as a single logical form state into multiple states. For example, you could have one action state handle form setup, a view state trigger form display, another action state handle data binding and validation, and another process form submission. This would be a bit more verbose but would also give you more control over how you respond to specific results of fine-grained actions that occur within the flow.
Subclassing hooks:
- A important hook is {@link #createFormObject(RequestContext) createFormObject}. You may override this to customize where the backing form object instance comes from (e.g instantiated transiently in memory or loaded from a database).
- An optional hook method provided by this class is {@link #initBinder(RequestContext,DataBinder) initBinder}. This is called after a new data binder is created.
- Another optional hook method is {@link #registerPropertyEditors(PropertyEditorRegistry)}. By overriding it you can register any required property editors for your form. Instead of overriding this method, consider setting an explicit {@link org.springframework.beans.PropertyEditorRegistrar} strategy as a more reusable way to encapsulatecustom PropertyEditor installation logic.
- Override {@link #validationEnabled(RequestContext)} to dynamically decide whether or not to do validation basedon data available in the request context.
Note that this action does not provide a referenceData() hook method similar to that of Spring MVC's SimpleFormController
. If you wish to expose reference data to populate form drop downs you can define a custom action method in your FormAction subclass that does just that. Simply invoke it as either a chained action as part of the setupForm state, or as a fine grained state definition itself.
For example, you might create this method in your subclass:
public Event setupReferenceData(RequestContext context) throws Exception { MutableAttributeMap requestScope = context.getRequestScope(); requestScope.put("refData", lookupService.getSupportingFormData()); return success(); }
... and then invoke it like this:
<view-state id="displayCriteria"> <on-render> <evaluate expression="formAction.setupForm"/> <evaluate expression="formAction.setupReferenceData"/> </on-render> ... </view-state>
This style of calling multiple action methods in a chain (Chain of Responsibility) is preferred to overriding a single action method. In general, action method overriding is discouraged.
When it comes to validating submitted input data using a registered {@link org.springframework.validation.Validator}, this class offers the following options:
- If you don't want validation at all, just call {@link #bind(RequestContext)} instead of{@link #bindAndValidate(RequestContext)} or don't register a validator.
- If you want piecemeal validation, e.g. in a multi-page wizard, call {@link #bindAndValidate(RequestContext)} or{@link #validate(RequestContext)} and specify a {@link #VALIDATOR_METHOD_ATTRIBUTE validatorMethod} action executionattribute. This will invoke the identified custom validator method on the validator. The validator method signature should follow the following pattern:
public void ${validateMethodName}(${formObjectClass}, Errors)
For instance, having a action definition like this: <evaluate expression="formAction.bindAndValidate"> <attribute name="validatorMethod" value="validateSearchCriteria"/> </evaluate>
Would result in the public void validateSearchCriteria(SearchCriteria, Errors) method of the registered validator being called if the form object class would be SearchCriteria
. - If you want to do full validation using the {@link org.springframework.validation.Validator#validate(java.lang.Object,org.springframework.validation.Errors) validate}method of the registered validator, call {@link #bindAndValidate(RequestContext)} or{@link #validate(RequestContext)} without specifying a "validatorMethod" action execution attribute.
FormAction configurable properties
name | default | description |
formObjectName | formObject | The name of the form object. The form object will be set in the configured scope using this name. |
formObjectClass | null | The form object class for this action. An instance of this class will get populated and validated. Required when using a validator. |
formObjectScope | {@link org.springframework.webflow.execution.ScopeType#FLOW flow} | The scope in which the form object will be put. If put in flow scope the object will be cached and reused over the life of the flow, preserving previous values. Request scope will cause a new fresh form object instance to be created on each request into the flow execution. |
formErrorsScope | {@link org.springframework.webflow.execution.ScopeType#FLASH flash} | The scope in which the form object errors instance will be put. If put in flash scope form errors will be cached until the next user event is signaled. |
propertyEditorRegistrar | null | The strategy used to register custom property editors with the data binder. This is an alternative to overriding the {@link #registerPropertyEditors(PropertyEditorRegistry)} hook method. |
validator | null | The validator for this action. The validator must support the specified form object class. |
messageCodesResolver | null | Set the strategy to use for resolving errors into message codes. |
@see org.springframework.beans.PropertyEditorRegistrar
@see org.springframework.validation.DataBinder
@see ScopeType
@author Erwin Vervaet
@author Keith Donald