Jul 042013
 

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

Sample wizard dialog for license management.

Sample wizard dialog for license management.

  • 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, as you will see, 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 don’t like it).
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 leaving or closing the
browser page.

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())) return;
            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.

Sorry, the comment form is closed at this time.