| Apache Wicket > Framework Documentation > Reference library > How to do things in Wicket > General Application > Building wizard functionality |
Since Wicket 1.2 RC4, there is a wizard component in wicket-extensions. Examples
of using this can be found in wicket-examples/wizard.
There are generally two ways of doing this (I think):
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.
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.
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.
Easy Wizard
is a small wizard engine, based on principle of Finite State Machine (FSM). It is different from similar flow engines in that states and transitions are themselves objects and can have references to domain model objects. Another key difference is that Easy Wizard does not have its own state besides active/inactive, and refers to domain model when state change should be made. This makes it applicable for a web application, where a user can navigate to a web resource directly and can use Back and Forward browser buttons.
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.
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 ![]()