This Confluence has been LDAP enabled, if you are an ASF Committer, please use your LDAP Credentials to login. Any problems file an INFRA jira ticket please.

Child pages
  • Building wizard functionality
Skip to end of metadata
Go to start of metadata

Table of contents

Note

Since Wicket 1.2 RC4, there is a wizard component in wicket-extensions. Examples
of using this can be found in wicket-examples/wizard.

Copied from the mailinglist

There are generally two ways of doing this (I think):

  1. Page oriented. Create a page for each step, and record your status by either passing it to the step pages as you construct them.
  2. Panel oriented, using:
    1. 'replace'. This way you have one panel (e.g. 'mywizPanel'), where your step part of the wizard is in. On a next step, replace the current panel with the next one. The advantage of this is that it is flexible (doesn't matter how many steps you have, you can always replace the panel with an arbitrairy other one. You let Wicket handle back-button support (Wicket will record undo's using a memento like pattern).
    2. 'set/isVisible'. Here you create all instances of the panels you need beforehand, and put them all on the page (or wizard panel), using different names (e.g. 'myWizStep1' and 'myWizStep2'). The trick here is the set only the one visible that is currently active (thus all others are not rendered). The advantage of this is that you only create the instances once, and that you don't have to record changes for backbutton support. However, it consumes more memory upfront, and you have to know your panel steps beforehand.

With 2, I would record the wizard state with the parent (page or another panel).

Another thing that might be a good idea, is to use command patterns for navigation actions. For example, I use this for a project:

  public interface INavigationAction
  {
     void navigate(RequestCycle requestCycle);
  }

  /* action to navigate back to this page. */
  private final INavigationAction backToThisPageNavigationAction = new INavigationAction()
  {
     public void navigate(RequestCycle requestCycle)
     {
        requestCycle.setResponsePage(VenueListPage.this);
     }
  }

and then have something like:

   public VenueDetailsPage(Long venueId, INavigationAction backNavigationAction)
   {
      ...
      add(new Link("cancelButton")
      {
         public void onClick()
         {
            backNavigationAction.navigate(getRequestCycle());
         }
      });
      ...

Anyway, that's just an idea. You might come up with a more elegant pattern, or you might be happy with a tight coupling between the wizard steps.

Building panel-oriented wizard

First, why this way of implementing wizard is better than page-oriented style. When you post to an address1, then redirect to address2 and then display a view, most browsers do not add address2 to history list again and again (save for Opera). Therefore, it is impossible to access old wizard page simply by clicking browser's "Back" button. Also, because all views correspond to one address, it is impossible to navigate directly to a certain wizard page; it is only possible to navigate to the wizard as a whole. This approach simplifies navigation, error handling and makes wizard more robust.

What is a wizard?

A wizard is different from a flow. Flow defines a sequence of states or events, that should take place. Wizard defines a visual component, which helps to build a complex object. Wizard steps allow to cut one big lump of input data into smaller chunks, and to establish dependency of one data block from another.

Using Easy Wizard

note - Easy Wizard is a dead project. The two web pages that had info about it are no longer available. The most recent reference to it was from 2005.

Example: simple two-panel wizard

We will design a simple login wizard, consisting of two panels: Signup panel, where a user enters his login name and password, and Confirmation panel, which redisplays user's name to confirm. Of course, there is no real need to implement login procedure as a wizard, but this is just an example.

Let us start with WizardPanel class. Easy Wizard defines interfaces for state, transition and controller objects, and provides their implementation, that can be reused. We will use standard IWizard and IWizardTransition implementatations, and will reimplement IWizardStep interface in a Wicket panel. This is reasonable because we are building panel-oriented wizard, each panel represents wizard's step and contains data relevant to that step. Therefore, WizardPanel extends Wicket Panel and implements IWizardStep interface. Most of the implementation code is copied from WizardStep class. Now I think that maybe it would be better simply to reuse WizardStep class...

There are two steps/panels: signup and confirm. Signup panel contains a model that it uses to obtain username and password. This approach makes it simple not to accept input data, if the step is not a current wizard step.

In this example each panel contains a form and navigation buttons. These buttons are mapped to wizard events. "Next" button should be immediate as well, because a step should verify first, should it accept input or not. But Wicket does not have explicit method to submit a form (yet?).

WizardPage class is a main page of this sample. It creates wizard controller and steps/panels.

   public class WizardPage extends WicketExamplePage {

     protected static Log log = LogFactory.getLog(WicketExamplePage.class);

     LoginWizard wizard;

     // Array of wizard panels
     WizardPanel[] wizardPanels = new WizardPanel[2];

    /**
     * Constructor
     */
     public WizardPage()
     {
       setVersioned(false);
       WizardFormInputModelSignup rawModel = new WizardFormInputModelSignup();
       IModel wizardModel = new CompoundPropertyModel(rawModel);

       // Each panel is a wizard step. Initialization in the loop cannot
       // be used, because each step defines different model-related methods,
       // thus all steps have different types.
       wizardPanels[LoginWizard.STEP_SIGNUP] =
         new WizardPanelSignup("wp"+LoginWizard.STEP_SIGNUP, wizardModel, this);
       wizardPanels[LoginWizard.STEP_CONFIRM] =
         new WizardPanelConfirm("wp"+LoginWizard.STEP_CONFIRM, wizardModel, this);

       wizard = new LoginWizard(rawModel, wizardPanels);

       // Add all panels to wizard page
       for (int i = 0; i < wizardPanels.length; i++) {
         add(wizardPanels[i]);
       }
       updatePanels();
     }

     public void onCancel() {
       wizard = null;
       updatePanels();
     }

     public boolean onBack() {
       boolean movedBack = false;
       if (wizard != null) {
         movedBack = wizard.back();
       }
       updatePanels();
       return movedBack;
     }

     public boolean onNext() {
       boolean movedNext = false;
       if (wizard != null) {
         movedNext = wizard.forward();
       }
       updatePanels();
       return movedNext;
     }

     /**
      * Hide all panels except one that corresponds to current wizard step.
      * If wizard is not active or is disposed, then all panels are hidden.
      */
     public void updatePanels() {
       for (int i = 0; i < wizardPanels.length; i++) {
         wizardPanels[i].setVisible(
           wizard != null &&
           ("wp"+i).equals(wizard.getCurrentStepName())
         );
       }
     }
   }

The wizard controller is defined in LoginWizard class. It defines the transition from Signup step to Confirm step:

   wizardSteps[STEP_SIGNUP].addOutgoingTransition(
     new WizardTransition(this, "Login To Confirm", wizardSteps[STEP_CONFIRM]) {
       public boolean validate() {
         LoginWizard wizard = (LoginWizard)this.getWizard();
         WizardFormInputModelSignup model = wizard.getModel();
         return model.isValidNameAndPassword();
       }
     }
   );

If validate method of this transition returns true, then wizard moves from signup to confirm step, otherwize it stays on the current step, which is signup step in our case.

Easy Wizard has the option to create error messages when condition of a transition is validated. This example does not use this feature.

Sample code

Code is available here: http://www.superinterface.com/files/wicket-wizard-src.zip It is not pretty, but it works and shows the concept. Prettifying will follow (wink)

  • No labels