Skip to main content

Web Wizard Component, Part 1: The Model

March 15, 2005

{cs.r.title}









Contents
What Is a Wizard?
Rule Container
A Sample Wizard: New User Signup
Wizard Nodes
Wizard Edges and Wizard Controller
Summary

Web applications are often criticized for having somewhat simplistic user interfaces when compared to regular desktop programs. Elements such as tree views, tabbed notebooks, or wizards are a rare treat. Now, a decade later after HTML 2.0 introduced the <form> tag, we are stuck with the same assortment of basic control elements.

This stagnation did not happen because evil W3C folk refused to include fancy controls into the next version of HTML. The problem with complex controls is that besides displaying and inputting data, they need to manipulate interdependent data. The data manipulation usually involves server-side logic or database lookup, and often cannot be performed solely on the client side. Thus, a pure client-side tree or notebook control would not make much practical sense.

This article makes another approach to complex control elements for the Web, and shows how to implement a wizard control using the good old <form> element on the client side and a set of navigation rules on the server side. The navigation rules are fully detached from the user interface, providing better code reuse and allowing the testing of wizard rules programmatically.

The first part of the article shows how to define and test wizard navigation rules using nothing more than the standard JDK. The second part explains the ideas behind the user interface and shows how to implement them using the well known Struts framework.

What Is a Wizard?

A wizard is a UI component, designed to input data in portions step by step.
A wizard usually has the following qualities:

  • All wizard data compose a single transaction.
  • Steps are processed in sequence from beginning to end.
  • There is one starting step, several intermediate steps, and one ending
    step.
  • The wizard validates its state before advancing to the next step.
  • There can be several different paths to reach the ending step.
  • It is possible to navigate back to review and update values entered in
    previous steps.
  • A wizard can be cancelled before completion.

A wizard must execute its steps in the correct sequence. This is difficult to
achieve in a web application, because a user can jump from page to page, or can
leave the application, browse another web site, and then return back. It is possible to restrict a user to a certain step order by controlling page sequence. But a model-driven approach seems to be more robust, cleaner, and easier to implement, and provides better integration with the application domain model.

Let us try this approach using a real-life task of signing up a new user. The example code uses classes extracted from a proof-of-concept application and packaged in a small helper library for easier reuse. You do not need to know the exact implementation of these classes to understand the concepts discussed in the article. On the other hand, you can freely download this helper code, which I uninventively called "Easy Wizard," for use in your own projects.

The wizard is designed around the MVC pattern and
contains two major parts: Rule Container and UI
Wrapper
. Rule Container is the wizard Model; it
defines wizard steps and paths between them. UI
Wrapper is the Controller/View component of the
wizard, which obtains user input and renders wizard
pages according to the current model state.

Rule Container

Scientifically speaking, a wizard model is a directed acyclic graph (DAG) with weighted edges. Rule Container represents this graph with a modified adjacency list. Each node of the graph corresponds to one wizard step. A simpler description would read: a wizard is a linked list of steps.

A node either refers to or directly stores domain data relevant to the step.
Besides that, a node holds references to the adjacent nodes. A node is defined
by the WizardNode class. These are its most important methods:

  • addOutgoingEdge defines an outgoing edge for a node; a node
    can have several outgoing edges.
  • getOutgoingEdge returns the outgoing edge chosen for forward
    traversal.
  • getIncomingEdge returns the actual edge that was used to
    reach the node during course of action; this is used for backward traversal.

An edge defines the path from one wizard step to another and holds the
references to its source and target nodes. An edge is defined by the

WizardEdge
class, which has the following important methods:

  • getSource and getTarget return the source and
    target nodes for the edge, respectively; this information is used for
    traversal.
  • validate returns true if it is allowed to move along the
    path, defined by the edge.

The wizard controller holds the references to the source and current
nodes, and provides traversal functions. It may store or
reference domain data like nodes do. The wizard controller is defined by the Wizard
class, which has following important methods:

  • getSourceNode and getCurrentNode return the
    first and the current node of a wizard, respectively.
  • traverseForward and traverseBackward move to the next or to the previous node.

A Sample Wizard: New User Signup

Let us design a new user signup wizard. It will contain three nodes
corresponding to three steps: Identification, Personalization, and Confirmation,
as seen in Figure 1.

Signup Steps
Figure 1. Signup wizard steps

The first node is Identification, which accepts a name and a password of a
new user. This node contains a flag, which signals that a user wants to
provide additional personal information. Two outgoing edges are defined for
this node.

  • Identification-to-Personalization edge: If the name and password are valid,
    and the user wants to provide additional information, the wizard moves to
    the Personalization node.
  • Identification-to-Confirmation edge: If the name and password are valid, but
    the user did not choose to provide additional information, the wizard moves to
    the Confirmation node.
  • If either the name or password is invalid, the wizard does not move from this
    step.

The second (optional) node is Personalization, where a user writes about
his favorite movie and a book. Only one outgoing edge is defined for this
node.

  • Personalization-to-Confirmation edge: if the book and the movie are both
    set, then the wizard moves to the Confirmation node.
  • Otherwise, the wizard does not advance from this node.

The third and the last node is Confirmation. After this step is finished,
the wizard data is applied to the application domain model, and the wizard
object is disposed.

Wizard Nodes

The nodes of the signup wizard enhance the WizardNode class with
specific business properties. As an example, see the definition of
the Identification node below. It contains name and password of a prospective user,
as well as an "Option to Personalize" property, which instructs the wizard to collect
information about the favorite book and movie of the user.

public class NodeIdentify extends WizardNode {

  private String name;
  public String getName() {return name;}
  public void setName(String name) {
    this.name = name;
  }

  private String password;
  public String getPassword() {return password;}
  public void setPassword(String password) {
    this.password = password;
  }

  private boolean pOpt;
  public boolean getPersonalize() {return pOpt;}
  public void setPersonalize(boolean pOpt) {
    this.pOpt = pOpt;
  }

  public boolean validateNameAndPassword() {
    if ( name != null && password != null) {
      return true;
    } else {
      ((SignupWizard)getWizard()).getErrors().put(
          "loginwizard.badlogin",
          new String[] {name, password});
      return false;
    }
  }

  public NodeIdentify(IWizard value, String node) {
    super(value, node);
  }
}

Business data related to wizard steps is stored in the nodes. This is a
reasonable choice for this demo. If a user cancels the signup procedure, the wizard
is disposed and domain model is not updated.

A cleaner choice would be to directly refer to the main application's domain model.
This approach requires better integration with the domain model. It also indicates
that validation of Personalization-to-Confirmation path is somewhat unnatural--the validity of data should not depend on whether a certain UI was presented to a
user or not. But this is just an example.

Both of the edges that leave the identification node need to verify user name and
password. This common functionality is implemented in the node as the validateNameAndPassword method, which is called from both outgoing
edges. If either the name or password is invalid, an error message is generated.

The Personalization node looks similar to the Identification node. It only defines
two string properties: favMovie for favorite movie, and

favBook
for a favorite book. The source code for this node is not shown.

The Confirmation node does not define any properties. It is the last node of the
wizard. Its processNode method applies wizard data to the
application domain model when the wizard finishes.

public class NodeConfirmation extends WizardNode {

  public void processNode() {
    SignupWizard wizard =
      (SignupWizard) getWizard();
    ArrayList errors = wizard.getErrors();
    if (!wizard.isCompleted()) {
      wizard.setCompleted(
        UserAccounts.addUser(
          wizard.nodeIdentify.getName(),
          wizard.nodeIdentify.getPassword(),
          wizard.nodePersonal.getFavBook(),
          wizard.nodePersonal.getFavMovie(),
        )
      );
      if (!wizard.isCompleted()) {
        errors.put(
        "loginwizard.loginexists",
        new String[] {wizard.nodeIdentify.getName()}
        );
      }
    }
    return;
  }

  public NodeConfirmation(SignupWizard value,
                          String name) {
    super(value, name);
  }
}

Wizard Edges and Wizard Controller

The wizard controller extends the pre-defined Wizard class and controls
wizard execution. The signup wizard controller defines references to all wizard
nodes, exposing them as properties. Nodes can be looked up by their names, as well.

final NodeIdentify nodeIdentify =
  new NodeIdentify(this, "Identification Node");

final NodePersonal nodePersonal =
  new NodePersonal(this, "Personalization Node");

final NodeConfirmation nodeConfirmation =
  new NodeConfirmation(this, "Confirmation Node");

While a node is usually defined in a separate file, an edge can be defined as
anonymous class, because it has only one method to override: validate. The
order in which outgoing edges are added to a node, is important. The edges are
validated in the same order in which they have been added.

nodeIdentify.addOutgoingEdge(
  new WizardEdge("Ident-to-Personal",
                 nodePersonal) {
    public boolean validate() {
      return
        nodeIdentify.getPersonalize() &&
        nodeIdentify.validateNameAndPassword();
    }
  }
);

nodeIdentify.addOutgoingEdge(
  new WizardEdge("Ident-to-Confirm",
                 nodeConfirmation) {
    public boolean validate() {
      return nodeIdentify.validateNameAndPassword();
    }
  }
);

nodePersonal.addOutgoingEdge(
  new WizardEdge(this, "Personal-to-Confirm",
                 nodeConfirmation) {
    public boolean validate() {
      if ( nodePersonal.getFavBook() == null ||
           nodePersonal.getFavMovie() == null) {
        ((SignupWizard)wizard).getErrors().put(
          "loginwizard.badpersinfo", null);
        return false;
      } else {
        return true;
      }
    }
  }
);

Validation methods do not return error messages. Instead, errors are
accumulated in the wizard controller. During the view rendering phase, errors are
processed by the UI wrapper and converted into the format native to a particular
web framework.

The last thing left to do is to set up the source and current nodes. The wizard
controller accesses other nodes in the linked-list fashion.

sourceNode = nodeIdentify;
currentNode = sourceNode;

This is it! Now we can see how Rule Container works. It is easy to
compile and debug Rule Container, because it does not deal with a user interface
and is not tied to any particular web framework.

public static void main(String[] args) {
  SignupWizard wiz = new SignupWizard();
  WizardNode curNode = null;
  do {
    System.out.println("\nGo forward");
    wiz.traverseForward();
    curNode = wiz.getCurrentNode();
    System.out.println("Current node: " +
      curNode.getNodeName());

    if (curNode instanceof NodeIdentify) {
      NodeIdentify nodeIdentify =
        (NodeIdentify) curNode;
      nodeIdentify.setName("sysdba");
      nodeIdentify.setPassword("masterkey");
      nodeIdentify.setPersonalize(true);
    } else if (curNode instanceof NodePersonal) {
      NodePersonal nodePersonal =
        (NodePersonal) curNode;
      nodePersonal.setFavBook("Thumbelina");
      nodePersonal.setFavMovie("Terminator");
    }
  } while (!"Confirmation Node".equals(
      curNode.getNodeName()));

  do {
    System.out.println("\nGo back");
    wiz.traverseBackward();
    curNode = wiz.getCurrentNode();
    System.out.println("Current node: " +
      curNode.getNodeName());
  } while (!"Identification Node".equals(
      curNode.getNodeName()));
}

The first loop traverses forward from the Identification node through
the Personalization node to the Confirmation node. The second loop traverses all the way
back. The output will look like this:

Go forward
Current node: Identification Node

Go forward
Current node: Personalization Node

Go forward
Current node: Confirmation Node

Go back
Current node: Personalization Node

Go back
Current node: Identification Node

At first, we try to move forward from Identification node when its properties
are empty. The wizard evaluates the Identification-to-Personalization path first.
This path is rejected because the option to personalize user settings is not set.
Then the wizard evaluates the Identification-to-Confirmation path and rejects it, too, because
the name and password are not set.

After the name, password, and option-to-personalize are set, the wizard moves to the
Personalization node. After a favorite book and movie are set, the wizard moves to the
Confirmation node.

The second loop moves back, using the actual incoming edges. Backward traversal
does not require validation.

Summary

Now with Rule Container coded and tested, we've finished the more important (but at the same time, rather straightforward) part. The steps, validation rules, and traversal commands are essential concepts of a wizard, but they need a matching UI. The next part of this article will explain some basic ideas behind the wizard UI, and will show the possible implementation of these principles using the Struts framework.

You may think of a possible implementation yourself, taking into account the navigation model of web applications, the issues related to going back and forward or refreshing a page, the importance of model/UI synchronization, the classic industry problems like double submit, and other factors that make web app programming so different from desktop programming.

In the meantime, you can download the source code of the Signup Wizard Rule Container, and see how it works. In the end of the second part of this series, I will provide a link to the full source code of the signup wizard application, including Rule Container, UI Wrapper code, and JSP pages.

width="1" height="1" border="0" alt=" " />
Michael Jouravlev lives in California and has a degree in computer science from the Moscow Aviation Institute in Moscow
Related Topics >> Programming   |   Struts   |