Modeling Wizard Dialogs

Wizard dialogs are a very common design pattern for user interfaces, e.g. for the GUI of software installation tools. However, although wizard dialogs are so ubiquitious these days, component frameworks such as Swing or JSF do not provide any support for them, so you have to turn to a third party library or roll your own. In this blog posting, I am modeling an abstraction for internationalized wizard dialogs which is independent of the particular user interface technology and easy to implement and use, e.g. in Swing based desktop applications or JSF based server applications.

Concept

The basic idea of a wizard dialog is to to split a larger task into multiple small, navigatable steps:

  • The task may be to execute a parameterized batch job. Then each step would prompt the user for some parameters and the final step would submit the batch job for execution. Alternatively, the user may cancel the wizard dialog before the batch job gets executed.
  • The steps may depend on each other so that they represent the vertices in a directed graph of job states with the wizard dialog as its stateful engine.
  • For navigation, the wizard dialog displays user interface components which, if enabled, switch to the previous step or the next step. Depending on the user interface technology, a component to cancel the wizard dialog may need to be displayed, too.

Use Cases

  • A software installation wizard dialog may prompt the user to decide which components to install at which location and finally perform the software installation based upon these parameters.
  • A license management wizard dialog may prompt the user to decide if a license key for the software should get installed, displayed or (optionally) uninstalled. The next step may then depend not only on the user’s choice, but also whether a license key is currently installed or not and if it’s valid or not.

In either case, a Swing based wizard dialog may display three buttons which enable the user to switch to the previous step, the next step or cancel the wizard dialog altogether. In contrast, a JSF based wizard dialog need not display a cancel button because cancelling the dialog is simply done by leaving or closing the browser page.

Requirements

I am going to define an abstraction for wizard dialogs which, in addition to the use cases, must meet the following requirements:

  • The abstraction must not depend on a particular user interface technology so that there can be different implementations.
  • The abstraction must not depend on a particular language so that an implementation can be internationalized.
  • Each step should be represented by a state of a stateful engine which meets the following requirements:
    • The engine must not depend on a particular type of state so that the implementation can freely choose it.
    • The engine must maintain the current state.
    • The previous and the next state should depend on the current state and user input so that the engine walks the user through a directed graph of steps.

Design

I am going to apply a variant of the Model-View-Controller (MVC) pattern because it’s a natural fit for the implementation of a stateful engine for a user interface. Im also going to define the core abstractions as Java interfaces, not classes, because interfaces are most versatile when implementing the abstraction using different user interface technologies.

As a simplification to the MVC pattern, I will not apply the Observer pattern. That’s because (a) it adds a lot of complexity and constraints to the design and (b) you can almost always substitute it with the more powerful and simpler Decorator pattern (although this discussion deserves another blog posting).

Model

The wizard model is nothing but a holder for the current state and a map of views for each state:

public interface WizardModel<S, V> {

    S currentState();
    void currentState(S state);

    V view(S state);
    void view(S state, V view);
}

The current state is basically a property of the type parameter S, although I didn’t use the typical getCurrentState/setCurrentState naming pattern because I think it’s a thing of the past. The type parameter value is completely opaque, that is, the model does not know anything about this type. As you’ll see below, it is good practice to use an enum class as the value of this type parameter.

The map of views is basically a property of the type parameter V which is indexed by states. Again, I didn’t use the typical getView/setView name pattern and the value of the type parameter V is completely opaque.

This interface is accompanied by a reusable basic implementation:

public class BasicWizardModel<S, V> implements WizardModel<S, V> {

    public static <S extends Enum<S>, V> WizardModel<S, V> create(final Class<S> clazz) {
        return new BasicWizardModel<S, V>(new EnumMap<S, V>(clazz), clazz.getEnumConstants()[0]);
    }

    private final Map<S, V> views;
    private S state;

    protected BasicWizardModel(final Map<S, V> views, final S state) {
        this.views = Objects.requireNonNull(views);
        this.state = Objects.requireNonNull(state);
    }

    @Override
    public V view(S state) { return views.get(state); }

    @Override
    public void view(S state, V view) {
        views.put(state, Objects.requireNonNull(view));
    }

    @Override
    public S currentState() { return state; }

    @Override
    public void currentState(final S state) {
        this.state = Objects.requireNonNull(state);
    }
}

When subclassing this class, you need to provide the map for the views and the initial current state.

For convenience, a static constructor is provided which accepts an enum class as its sole parameter. It then passes an enum map and the first ordinal enum to the protected constructor. For example, with the following enum class for license management…

enum LicenseWizardState {
    welcome, install, display, uninstall;
}

… you could use the static constructor like this:

WizardModel<LicenseWizardState, MyView> model = BasicWizardModel.create(LicenseWizardState.class);

The static constructor saves you from typing the generic type parameters twice and the protected constructor retains the ability to use non-enum type states with this class if you want to.

View

The wizard view is another simple interface:

public interface WizardView<S> {

    S backState();
    S nextState();

    void onBeforeStateSwitch();
    void onAfterStateSwitch();
}

The (back|next)State methods enable the view to dynamically resolve the previous and next state, which is modeled as an opaque type parameter S again. If switching to a previous or next state is not available, e.g. because the user needs to provide some input first, then they need to return the current state.

The on(before|after)Switch methods are event methods which get called by the controller to indicate state change. A typical implementation may initialize and/or hide/display this view.

The wizard view is completely agnostic to the user interface technology. This enables you to use this abstraction with Swing, JSF or any other user interface technology, even a command line interface. Because of this and for the sake of brevity, there is no basic implementation provided here.

Controller

Last, but not least, the wizard controller interface looks like this:

public interface WizardController {

    boolean switchBackEnabled();
    boolean switchNextEnabled();

    void switchBack();
    void switchNext();
}

The boolean switch(Back|Next)Enabled method return true if and only if the respective (back|next)State method of the WizardView interface returns a different state than the current state. This may be used to enable or disable the respective button of the user interface.

Finally, the switch(Back|Next) methods switch the state of the engine from the current state to the previous or next state respectively. Calling these methods cause the on(Before|After)StateSwitch methods of the WizardView interface to be called.

In a typical implementation, the switch methods are the actions for the “back” and “next” buttons. You may wonder then why there is no action for a “cancel” button. This is because whether or not a “cancel” button is displayed is an implementation detail: In a desktop user interface (e.g. Swing based), it’ll most likely exist, whereas in a web app (e.g. JSF based), this wouldn’t make sense because cancelling the dialog is simply done by abandoning the browser session.

Although the contract for this interface implicitly depends on the WizardModel and WizardView interfaces, it doesn’t have an explicit dependency on these interfaces. Hence it also doesn’t know about their type parameters, that is it doesn’t know anything about the type of the wizard states or wizard views.

Again, this interface is accompanied by a reusable basic implementation:

public abstract class BasicWizardController<S, V extends WizardView<S>> implements WizardController {

    public static <S, V extends WizardView<S>> WizardController create(final WizardModel<S, V> model) {
        return new BasicWizardController<S, V>() {

            @Override
            protected WizardModel<S, V> model() { return model; }
        };
    }

    protected abstract WizardModel<S, V> model();

    @Override
    public final boolean switchBackEnabled() {
        return !backState().equals(currentState());
    }

    @Override
    public final void switchBack() { switchTo(backState()); }

    @Override
    public final boolean switchNextEnabled() {
        return !nextState().equals(currentState());
    }

    @Override
    public final void switchNext() { switchTo(nextState()); }

    private void switchTo(final S newState) {
        if (!newState.equals(currentState())) {
            fireBeforeStateSwitch();
            currentState(newState);
            fireAfterStateSwitch();
        }
    }

    protected final void fireBeforeStateSwitch() {
        currentView().onBeforeStateSwitch();
        onBeforeStateSwitch();
    }

    protected void onBeforeStateSwitch() { }

    protected final void fireAfterStateSwitch() {
        onAfterStateSwitch();
        currentView().onAfterStateSwitch();
    }

    protected void onAfterStateSwitch() { }

    protected final V currentView() { return view(currentState()); }

    protected final V view(S state) { return model().view(state); }
    protected void view(S state, V view) { model().view(state, view); }

    protected final S currentState() { return model().currentState(); }
    private void currentState(S state) { model().currentState(state); }

    protected final S backState() { return currentView().backState(); }
    protected final S nextState() { return currentView().nextState(); }
}

BasicWizardController is an abstract class because its model property method needs to be implemented in a subclass. For convenience, there is again a static constructor which accepts a generic wizard model as it’s sole parameter and implements a subclass for it.

Note that this time the type parameter V needs to extend WizardView<S>. This is required because the controller needs to ask the view for the previous and next states of the current state. This is a deviation from the pure MVC pattern where the controller would have to turn to the model to figure this. However, for this use case the deviation is simpler to implement.

The remainder of this class is pretty straightforward. The on(Before|After)StateSwitch methods are empty templates which mean to do the same thing as the equal named methods of the WizardView interface, yet they are called on the controller, not the view. Depending on the user interface technology, this may be more convenient. E.g. in a Swing based wizard, the onAfterStateSwitch method may be overridden to switch the tab of the CardPanel which hosts the JPanels for the different views.

Conclusions

That’s it, just three interfaces with just four methods each and two basic implementation classes! Given such a leightweight (or thin) abstraction, you may ask why you would want to use it? Here are some benefits:

  • Reusability: The abstraction is agnostic to the user interface and language, so it can be applied to many different problem domains.
  • Simplicity: The interfaces have just four methods each and the rather complex Observer pattern has been carefully avoided.
  • Don’t-repeat-yourself: The basic implementation classes save you from repeating and testing essential functionality.
  • Proven Practice: The abstraction has been used to implement wizard dialogs for license management based on Swing and JSF in my TrueLicense product.

Enjoy!