Wizard Pages
The wizard defines five JSP pages: one page for each wizard step, and two stub
pages. All pages have the following common features:
- HTML forms are submitted with the
POST method.
- Input is always submitted to the same URL, which is served by the same
Struts action.
- Input controls have access to wizard data through nested
properties.
- Each button submits a command parameter.
- Errors are not cleared on refresh, or when a user leaves the application
and returns later.
Here is the page corresponding to the first wizard step, Identification.
Other pages look similar.
<html:form action="/signupWizard.do">
User Name: <html:text name="WizardForm"
property="signupBean.userName"/><br>
Password: <html:text name="WizardForm"
property="signupBean.userPassword"/><br>
Personalize: <html:checkbox name="WizardForm"
property="signupBean.personalize"/><br>
<input type="submit" name="cmd" value="Cancel">
<input type="submit" name="cmd" value="Next">
</html:form>
Web Flow Configuration
Finally, the wizard flow, defined in the struts-config.xml file:
<struts-config>
<form-beans>
<form-bean name="WizardForm"
type = "com.superinterface.loginwizard.
struts.WizardForm"/>
</form-beans>
<action-mappings>
<action path="/signupWizard"
type="com.superinterface.loginwizard.
struts.WizardAction"
name="WizardForm"
scope="session">
<!-- Obtain input -->
<forward name="Wizard Loop"
path="/signupWizard.do" redirect="true"/>
<forward name="Done"
path="/userPage.do" redirect="true"/>
<forward name="Canceled"
path="/signupWizard.do" redirect="true"/>
<!-- Show view -->
<forward name="Identification Node"
path="/WEB-INF/JSP/signup_start.jsp"/>
<forward name="Personalization Node"
path="/WEB-INF/JSP/signup_details.jsp"/>
<forward name="Confirmation Node"
path="/WEB-INF/JSP/signup_confirm.jsp"/>
<forward name="Not Logged In Stub"
path="/WEB-INF/JSP/stub_loggedoff.jsp"/>
<forward name="Logged In Stub"
path="/WEB-INF/JSP/stub_loggedin.jsp"/>
</action>
<action path="/userPage"
type="org.apache.struts.
actions.ForwardAction"
scope="request"
parameter="/WEB-INF/JSP/user_page.jsp"/>
</action-mappings>
<controller nocache="true"/>
</struts-config>
The most important mapping is Wizard Loop. It is used to redirect the browser
back to the wizard action after input data is submitted. The action then chooses the
appropriate view and forwards to the JSP page.
The Not Logged In Stub and Logged In Stub mappings are used to display
stub pages, when the wizard's Rule Container is not initialized. The "Not logged in" page
allows a user either to log in or to start the signup process. The "Logged in" page
displays the name of the current user and allows him to log out. Figure 2 shows what the stub pages look like.

Figure 2. "Logged in" and "Not logged in" stub pages
If the Rule Container is active, then Identification Node, Personalization Node, and
Confirmation Node mappings are used to forward to the page corresponding to the
current wizard step. Figure 3 shows the wizard pages.

Figure 3. Signup Wizard steps
The Canceled mapping in this configuration transfers back to the wizard
action. Alternatively, this mapping may redirect to some other page, informing
the user about the failure.
The Done mapping is the only mapping that goes outside of the wizard's action; it
shows the user's home page after a successful login.
Controlling the Browser
Signup Wizard improves the well known Redirect-After-Post technique, using
a couple of tricks.
The first is to serve different content from the same location. And by saying
"same" I mean exactly the same, including the number, names, and content of request query parameters.
Internet Explorer and Mozilla/Firefox build session history based on resource
location, and do not include resources from the same location into the history.
Another trick is redirection: according to HTTP specs, when a browser is
redirected from a POST request, the resource identified by original request must
not be stored in the session history. Some browsers like Internet Explorer go
further and do not cache response for original request even if the request has the GET type.
These tricks result in a neat effect: a browser thinks that it keeps reloading
the same page, so it does not enable its Back and Forward buttons. Thus, the browser prevents
a user from going back to see the stale data or to resubmit the stale form.
If this approach does not work with a particular browser, and the browser accumulates every page in its history, then the application falls back to the Redirect-After-Post pattern.
The Signup Wizard was tested on Windows 2000 with Internet Explorer, Netscape
Navigator, Opera, and Firefox (make sure that you use the official release of
Firefox, which fixes the bug with no-store cache-control header).
Opera is the bad boy; it tries to cache everything. It interprets the HTTP standard
differently than Internet Explorer and Firefox, and does not reload a page when
a user navigates back. Thus, it is possible to resubmit a stale form. The Signup
Wizard tries to check for View-Model consistency to prevent it from
accepting data from the wrong page.
Conclusion
In this article, we have discussed a server-centric approach to web
applications. We developed a multi-page wizard component, which provides
synchronization of View and Model, separates input from output, separates one
view from another, disables page caching, and tries its best to control the
browser's session history. In short, we developed the component that has as
few page interdependencies as possible and as much server state as possible.
It is up to you to decide if the compromises justify the outcome. Software is created for consumers. So if
a certain approach makes an application more robust and a user experience more
trouble-free, than it definitely worth some extra computer cycles.
The trick with the same resource location, described in the article, may not be appropriate for every web application.
Signup Wizard is not a regular application. A Signup Wizard instance exists only while a user performs a one-time job. It does not retain its state for long; its pages should not be bookmarked. All wizard pages share the same data. The Signup Wizard does not accept direct page location from a browser.
A regular CRUD (CReate, Update, Delete) application is different. Its data is usually persisted in the database and can be loaded at any time. It may be important to
be able to bookmark a page; thus, a URL should fully describe a particular item. It is also important
to provide the correct behavior for simple page refresh, for reloading a form with an error message, and for
navigating back and forth. Thus, request query parameters become an important component of an
application and cannot be omitted, like they are in the Signup Wizard.
Resources
Michael Jouravlev lives in California and has a degree in computer science from the Moscow Aviation Institute in Moscow