Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Struts Wiki" for change 
notification.

The following page has been changed by MichaelJouravlev:
http://wiki.apache.org/struts/DataEntryForm

New page:
#format wiki
#language en

Data entry form is the most common use case for any interactive application. If 
entered data is correct, application accepts is and often shows a success 
message. Otherwise, application redisplays the data entry form along with 
relevant error messages, operator fixes the errors and submits the form again. 
In this use case a user is able to fill out the form in several attempts, while 
the form preserves data entered by the user on each attempt.

Struts framework is built around the Front Controller pattern. This pattern 
gives no guidelines on how consecutive requests should interact with each other 
and with business object, neither it specifies how the application should 
manage view state and state of the business object.

Left to their own devices and constrained with framework limitations, web 
developers devised the pattern for building interactive forms with Struts. For 
the lack of a better approach this pattern became a standard practice in Struts 
community.

== Setup/submit pattern ==

In general, an interactive web application can operate in two phases. On input 
phase (or accept phase) a browser sends user input to a web resource, usually 
by submitting an HTML form. On output phase (or render phase) web resource 
displays a view matching its state.

Struts exploits this idea using two types of actions: 
 * setup action (pre-action, output action, render action) is used to prepare 
output data before displaying a JSP page;
 * submit action (post-action, input action, accept action) is used to accept 
user input.

Setup/submit action pattern is a standard practice for building interactive 
application with Struts.

It is common to have several actions of either type for a web resource. For 
example, if you deal with Customer resource, you are likely to define two setup 
actions: viewCustomer.do and editCustomer.do and three submit actions: 
addCustomer.do, updateCustomer.do and deleteCustomer.do. 

Setup actions load data from database and queue it into one or more arbitrary 
objects located in the request or session scope. Submit actions process input 
data and redisplay the same data entry form if errors has been found in the 
input. If input does not contain errors, submit actions accept it and forward 
to a success page. Success page often corresponds to a logically different web 
resource.

This approach has its issues, though:
 * One web resource is defined with several action mappings in the 
struts-config.xml file as well as with several Java classes.
 * Output data is scattered in an uncontrolled manner throughout request and 
session scope
 * In case of error the data entry form is redisplayed by a submit action; that 
opens a whole can of worms:
  * If input data is invalid and autovalidation is turned on, submit action is 
never get called and cannot affect the workflow.
  * One page is represented with two different URLs in the browser.
  * An attempt to refresh a page after it has been submitted and then 
redisplayed, causes double submit.
 * Submit action that forwards to a success page corresponding to another web 
resource leads to a spaghetti code both in Java code as well as in 
struts-config.xml file.

The remainder of this page shows how to improve classic Setup/Submit pattern 
step by step.

== Step 1: Share one ActionForm as input/output buffer ==

!ActionForm was originally designed to collect request data. Despite of that, 
online poll shows that about 60% of Struts users employ !ActionForm as the 
holder of output data as well. This makes a lot of sense:

 * Setup action populates !ActionForm and its nested properties with business 
data and forwards to a JSP page.
 * JSP page displays a data entry form filled in with information from the 
!ActionForm.
 * When HTML form is submitted, the !ActionForm is populated automatically by 
Struts with values from the request.
 * If input is invalid, data entry form is redisplayed; it will have contained 
data submitted by a user on a previous step.

Therefore, instead of queueing output data to arbitrary objects in request or 
session scope, a setup action has to use an !ActionForm as the holder of 
input/output data. Mappings of both setup action and submit action should refer 
to the same !ActionForm in their "name" attribute.

Shared !ActionForm makes it easy to preserve incremental changes made by a user 
in a data entry form.

== Step 2: Do not forward to a page that does not belong to current web 
resource ==

A submit action should not forward to a success page belonging to another 
logical web resource. Instead, it should forward (or even better, redirect) to 
a setup action of the success page. This allows to break a convoluted M:M 
relationship between actions and pages down to simple and observable 1:M 
relationship. That is, several pages should correspond to one web resource. Not 
vice versa!

Another benefit of transferring to a setup action is that now you don't care 
about what will be displayed on success page. You do not have to select a 
proper page and you do not need to setup output data for that page. This is the 
business of the web resource you are transferring to, this is what its setup 
action should do.

With this simple change an application can now be broken into separate 
independent chunks. There is no need to build inflexible "flow" from one page 
to another. Proper page is selected and displayed by its respective setup 
action.

== Step 3: Do not use autovalidation ==

Turn autovalidation off and perform validation manually. This ensures your full 
control over input data and over the workflow. With autovalidation turned off 
your action class will always be called, so you can make a better decision what 
to do in case of error, whether you want to redisplay the same data entry form, 
shoud you transfer the control to another web resource or maybe you need to 
modify your business object instead.

Autovalidation is used together with "input" attribute of action mapping in 
struts-config.xml file. If !ActionForm.valiadate returns non-empty error object 
during autovalidation, Struts forwards to location defined in the "input" 
attribute. Usually, it is the same data entry form that was just submitted. 
Therefore, the same form can be represented in the browser with two different 
URLs: one URL when it is rendered by a setup action, and another URL when it is 
redisplayed by a submit action. In most cases the browser is forwarded to the 
page, not redirected, so an attempt to refresh a page after it has been 
redisplayed causes double submit. 

These are techniques worth considering when you need to redisplay data entry 
form:
 * Forward to data entry page from submit action, use Struts token feature to 
catch the resubmit. This approach does not protect a user from an unfriendly 
POSTDATA message and it does not help with two URLs situation. Use this 
approach if your !ActionForm is request-scoped and you want to reuse data 
entered by a user.
 * Redirect to a setup action (not directly to a page) appending a business 
object ID and another relevant information to the target URL. This approach 
eliminates resubmit on page refresh, and it solves dual URLs issue. Use this 
option if you want to provide the better and cleaner user experience but you 
don't want to use session-scoped !ActionForms. Your setup action must be able 
to initialize the !ActionForm using ID and another request parameters that you 
have sent in redirected request. If your data entry form is quite large, 
sending all information in a redirected request may not be feasible.
 * Redirect to a setup action (not directly to a page), keeping data entered by 
a user in a session-scoped !ActionForm. This approach is user-friendly, it  
eliminates resubmit on page refresh, solves dual URLs issue and provides a 
clean redirected URL. The only downside of it is keeping !ActionForm in the 
session between requests; this may not be desirable for some applications.

== Step 4: initialise ActionForm manually in setup action ==

!ActionForm should be populated only on submit phase. To protect !ActionForm 
from unintended modification by setup action, do not set "name" attribute in 
setup action mapping. This will prevent Struts from instantiating and 
populating the !ActionForm. To setup !ActionForm data you need to instantiate 
it yourself and to put in into appropriate scope, either into request or into 
session. You will also have to refer to the !ActionForm explicitly using "name" 
attribute of your input elements.

== Step 5: consolidate your actions; use one of the DispatchAction flavors ==

A simple web resource like Customer can have couple of setup action mappings 
like viewCustomer.do and editCustomer.do and several submit action mappings 
like addCustomer.do, updateCustomer.do and deleteCustomer.do.  Of course, this 
does not mean that you need to define five corresponding Java classes. It would 
be great if you could reduce number of action classes down to two: one setup 
action and one submit action per web resource. It would be also great to reduce 
number of action mappings in struts-config.xml file.

First, let us deal with setup actions. You can combine two aforementioned 
mappings into one setupCustomer.do and to differentiate between edit and view 
modes using a request parameter, like setupCustomer.do?mode=edit . Whether to 
use this approach or not depends on your idea of "clean URLs" and other factors.

Dealing with submit actions is a more straightforward process, just use one of 
!DispatchAction flavors. Struts core library includes several dispatcher 
actions: !DispatchAction, !LookupDispatchAction an !MappingDispatchAction. 
Despite of this variety, all these actions have their deficiences:
 * !DispatchAction requires for a submit button to have the same caption as the 
handler method name; otherwise one has to use Javascript.
 * !LookupDispatchAction is heavy, uses inverted reference to resource files.
 * !MappingDispatchAction can be used to dispatch only one method.

Therefore I recommend using !ParameterMappingDispatchAction as your entry point 
to a web resource. You would define all event handlers in the "parameter" 
attrubute of action mapping, including default method name. Then, on the data 
entry form, you would set "name" attributes of various submit buttons to 
corresponding event name, like "edit", "view" or "delete".

== Step 6: Reduce number of actions down to one ==

!ParameterMappingDispatchAction mentioned in the previous section, is pretty 
smart. It can detect the logical type of request that it receives: is it a 
render request or submit request. It does this by looking for event in the 
request. 

If !ParameterMappingDispatchAction finds event in the request, it calls the 
corresponding method handler. This is a submit phase. 

If !ParameterMappingDispatchAction cannot find event in the request, it calls 
default method. This is a render phase, so you can prepare the !ActionForm and 
render a page.

As you can see from the picture, the whole web resource is now controlled with 
only one action class and has only one URL. Neat! A web resource can have 
several JSP pages corresponding to it, so you can choose a proper one depending 
on the incoming event and on the state of the resource. For example, if you 
create a login component, you may decide to have two pages: Login and Logout, 
and you can display either one depending on user login status.

Remember, the primary object in a web application is a web resource, not a 
page. A page is just a view; one web resource can have several views. Do not 
build application around pages, build it around web resources. In Struts, web 
resources are represented with Action and !ActionForm classes.

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to