Struts Applications
 
   

Struts Dialogs: WizardAction

PDF
PDF

Overview

WizardAction and a corresponding WizardForm provide integration of Easy Wizard flow engine with Struts. Easy Wizard allows to create robust sequences of web pages, known as wizards.

See this online demo, which simulates a signup for a new user accout. Play with the wizard, try to refresh any page, to use browser navigation buttons, to go back and forward; leave the wizard, navigate somewhere else, then return back and check its state; try to cancel wizard, to start it again, to use invalid values, just try to break it. If you like what you see, read on.

Easy Wizard has modular architecture and consists of two components: Rule Engine and Wizard Manager.

  • Rule Engine has no user interface, and is portable across different presentation environments.
  • Wizard Manager integrates Rule Engine with particular web framework.

Rule Engine

Rule Engine is the core wizard component, which does not depend on a particular presentation framework. It can be built and tested in headless mode, using only standard Java SE classes.

Rule Engine contains definitions of wizard steps and transitions, and is customized for every specific flow. Rule Engine interacts with Wizard Manager by means of Wizard Controller object.

Wizard Manager

Wizard Manager is the component, which adapts Rule Engine to a particular web framework. Easy Wizard provides a custom Wizard Manager for every framework it supports. The task of Wizard Manager is to accept input from a user and to render wizard pages.

Easy Wizard architecture

Integration with Struts

WizardAction and WizardForm classes provide functionality of a Wizard Manager. They handle requests and responses, accept user input, dispatch input event and data to the Rule Engine, collects error messages and display a page, appropriate to current wizard state.

WizardAction channels user commands to Wizard Controller. If Forward transition was requested, Wizard Controller validates all outgoing transitions for current step, and chooses a valid one. Wizard Controller changes the state of Rule Engine and returns back error messages if any.

After receiving result from Wizard Controller, WizardAction selects a proper page to display according to current state of Rule Engine.

Creating a wizard

To create a wizard you need to peform two steps:

  • Define wizard rules by customizing Rule Engine
  • Define interaction with a user by creating custom Wizard Manager

To accomplish first task, please consult Easy Wizard project and Easy Wizard documentation.

You do not need to download Easy Wizard separately. You will find all source code in Struts Dialogs distribution. Easy Wizard source code in src\org\superinterface\wizard directory, WizardAction and WizardForm in src\net\sf\dialogs\actions\wizard directory, and Signup Wizard sample code in src\net\sf\dialogs\samples\wizardaction directory.

The rest of this document describes how to create a Wizard Manager using WizardAction and WizardForm, and how to integrate it with Rule Engine that you should have developed beforehand.

Accessing Rule Engine from Struts

Considering, that you have already created a custom Rule Engine for Signup Wizard, we will now integrate it with Struts.

To store and access Rule Engine you need to use an action form, which implements IWizardManager interface. This interface has the following methods:

  • Map getWizardErrors(); - returns errors, accumulated by wizard during processing of input data.
  • String wizardCancel(); - cancels wizard.
  • String wizardBack(); - moves one step back if possible.
  • String wizardNext(); - moves one step forward if possible.
  • void disposeWizard(); - disposes wizard and performs housekeeping tasks.
  • boolean isCompleted(); - returns true if wizard was completed or was never instantiated.
  • String getWizardView(); - returns string mapping of a wizard page, corresponding to current wizard state. By convention, a wizard step is used as page mapping.

The easiest way to use IWizardManager is to employ predefined WizardForm class, which already implements this interface.

Using WizardForm

First thing you need to do is to decide how you are going to instantiate and initialize the Wizard Controller. The easiest way is to do it in the reset() method. The following code uses protected field wizard, defined in WizardForm. Direct access to this field seems easier than using method like getWizard.

 public void reset(ActionMapping mapping, HttpServletRequest request) {
   // Do not forget to call superclass
   super.reset(mapping, request);

   // This wizard does not have stub pages, thus initialize it every time
   // a user navigates to this action and wizard controller does not exist.
   if (wizard == null) {

     // Create new Wizard Controller instance;
     // Use internal to Rule Engine object for error messages.
     wizard = new SignupWizard(null);

     // Use this action form as wizard event listener;
     // it stores account info in the account database when
     // wizard is about to finish.
     wizard.addListener(this);
   }
 }

To provide access to fields of your wizard you need to define a getter for a concrete Wizard Controller:

 public SignupWizard getSignupWizard() {
   return (SignupWizard) wizard;
 }

Another thing you are likely to do is to define a listener for wizard forward/back operations by implementing IWizardListener interface. This allows you to catch the event when wizard is about to change state, so you can to prevent the state change if needed. For example, the following code saves new user name and password into user accout table, when wizard is about to finish. If username and password cannot be persisted, error message is generated and wizard is not allowed to finish.

 public boolean onTransition(int event) {

   // Not finishing wizard yet ==> not interested
   if (event != IWizardListener.STEP_LAST) return true;

   // Wizard is about to complete ==> store new user account
   if (UserAccounts.addUser( session,
     getSignupWizard().getStepSignup().getName(),
     getSignupWizard().getStepSignup().getPassword(),
     getSignupWizard().getStepDetails().getSecurityAnswerId(),
     getSignupWizard().getStepDetails().getSecurityAnswer(),
     true)
   ) return true;

   // Account was not stored ==> generate error, do not dispose wizard
   getWizardErrors().put("loginsignupcontrol.persisterror",
     new String[] {getSignupWizard().getStepSignup().getName()});
   return false;
 }

That is all for integrating Wizard Controller. All is left is to define an action mapping for the wizard in struts-config.xml file.

Wizard events and return mappings

WizardAction defines the following user event keys and method handler names:

"DIALOG-EVENT-CANCEL" --> onCancel
Cancel wizard; assigned to "Cancel" button.

"DIALOG-EVENT-BACK" --> onBack
Tries to move one step back; assigned to "Back" or "Previous" button.

"DIALOG-EVENT-NEXT" --> onNext
Tries to move one step forward or to finish a wizard, if current page is the last page of the wizard; assigned to "Back" or "Forward" or "Done" button.

WizardConstants class defines the following view mappings used to select destination after a user event was processed, and wizard state was updated:

String MAPPING_ON_CANCEL = "ON-CANCEL";
Wizard was canceled by user.

String MAPPING_ON_DONE = "ON-DONE";
Wizard successfully finished.

String MAPPING_ON_BACK_SUCCESS = "ON-BACK-SUCCESS";
Wizard moved one step back.

String MAPPING_ON_BACK_FAILURE = "ON-BACK-FAILURE";
Failed to move one step back.

String MAPPING_ON_NEXT_SUCCESS = "ON-NEXT-SUCCESS";
Successfully moved one step forward, but not finished yet.

String MAPPING_ON_NEXT_FAILURE = "ON-NEXT-FAILURE";
Failed to move to next step.

Below is the definition of wizard action mapping.

 <action path="/wizardaction"
         type      = "net.jspcontrols.dialogs.actions.wizard.WizardAction"
         name      = "wizardform"
         scope     = "session"
         validate  = "false"
         parameter = "DIALOG-EVENT">

     <!-- Where to hand control over after input phase (POST) -->

     <forward name="MAPPING_ON_BACK_SUCCESS" path="/wizardaction.do" redirect="true"/>
     <forward name="MAPPING_ON_BACK_FAILURE" path="/wizardaction.do" redirect="true"/>
     <forward name="MAPPING_ON_NEXT_SUCCESS" path="/wizardaction.do" redirect="true"/>
     <forward name="MAPPING_ON_NEXT_FAILURE" path="/wizarderroraction.do" redirect="true"/>

     <forward name="ON-CANCEL" path="/allsamples.html" redirect="true"/>
     <forward name="ON-DONE" path="/wizard-userpage.jsp" redirect="true"/>

     <!-- Which page to load on the output phase or on refresh (GET) -->

     <forward name="Signup Node" path="/wizard-signupstart.jsp"/>
     <forward name="Details Node" path="/wizard-signupdetails.jsp"/>
     <forward name="Confirmation Node" path="/wizard-signupconfirm.jsp"/>
 </action>
 <action path="/wizarderroraction" forward="/wizard-error.jsp"/>

Sample code

See full source code of user signup wizard in src\net\sf\dialogs\samples\wizardaction\wizardsubclassed directory of the downloaded code.

Live Demo