How to Create Web Pages With Forms (Java Developer)

Draft 0.3
June 20, 2000
Ted Lee (tlee@startups.com)

1. Applicability

Read this document when any of the following are true:

Don't read this document when any of the following are true:

If this document doesn't meet your needs, you may want to look at:

2. Steps for Creating a Web Page With A Form

2.1 Draw a state diagram showing the various transitions between web pages.

You will find this to be extremely useful later on.  You may be able to simply obtain the state diagram from an information architect, but most likely you will have to deduce it from site mockups or specifications of site behavior.

2.2 Create MVC components for each state

For each state, you will create three types of components--a Model, one or more Views, and a Controller.  A view is simply a jsp, which fetches its content from a java object called the model.  The controller is a servlet that handles transitions between states.

2.2.1 Create the view JSP(s)

2.2.2 Create the model Java class or classes

2.2.3 Create the controller servlet

 

 

3.  Design Discussion

Web Pages Are A Set Of Discrete States

A sequence of web pages can be thought of as a sequence of discrete states:

A single state is represented by Model-View-Controller components, using the following naming convention:  <state>.jsp, <state>Servlet, and <state>Model.

View (<state>.jsp)

The view contains the presentation of a particular state.  It is implemented as a jsp which has some dynamic content and contains a form.  The dynamic content is obtained from the model, usually via simple jsp evals (e.g. <%= model.getSomeAttribute() %>) or bean properties.  When the form is submitted, this indicates a possible state change.  The form is submitted to the controller component, which is a servlet named for the state.

Controller (<state>Servlet)

The servlet's responsibility is to update the model and to figure out which state to go to next.  It does this via very simple conditional logic and potentially some help from model objects.  It may need to instantiate new model objects or modify existing model objects corresponding to the next state.

Model (<state>Model)

The model represents the presentation-independent logic of the state, and is implemented as a Java object.  The model may actually be a facade object which may delegate much of its work to other persistent objects such as User and Address.  However, the view does not depend on any object other than the model, so from the point of view of the view the model contains all the logic.  Model objects may live in page scope, or may sometimes live in session scope, but are not persistent.

Rationale for Using MVC

We expect to have at least two distinctly different classes of developers working on the site simultaneously:  web designers and Java programmers.  We'd like to have these developers work as independently as possible.  For instance, it would be undesirable to require a Java programmer to recode and recompile servlets in order to make a color change to the look of a web page.  It would be equally undesirable to require the web designer to have to understand arcane jsp code in order to make such a change.

Goals:

MVC allows for a reasonable separation between logic and presentation, which helps web designers and Java programmers do what they do best.  Having one MVC triplet per web application state simplifies conditional logic and improves maintainability.

The main consequence of using the MVC system is that there will be more files than if we didn't use MVC.  For every state, there are at least three files that are involved.  However, each file should be much simpler and easier to understand than if these things were in fewer files, so it seems to be an acceptable tradeoff.
 

Example 1:

Example ClientQualInfoServlet code fragments:

Having one servlet represent multiple states (e.g. the existing ClientQualProcessingServlet) can lead to extremely complicated conditional code which is very difficult to maintain and debug.  The proposed system of having one servlet represent a single state means that its logic is relatively simple, since there are a limited number of state transitions for any given state.
/**
 * Performs simple conditional logic to determine which state to
 * transition to.  
**/
public void doPost(HttpServletRequest request, 
                   HttpServletResponse response) throws ServletException {
    if (wasCancelChosen(request)) {
        discardClientQualInfoModel(request);
        enterState("index.jsp", request, response);
    } else { // next was chosen
        updateClientQualInfoModel(request);
        if (getClientQualInfoModel(request).isInvalid()) {
            enterState("ClientQualInfo.jsp", request, response);
        } else {
            createClientQualConfirmModel(request);
            enterState("ClientQualConfirm.jsp", request, response);
        }
    }
}

private void discardClientQualInfoModel(HttpServletRequest request) throws ServletException {
    //
    // Remove the model from the session to allow the garbage collector
    // to reclaim memory.  
    //
    HttpSession theSession = request.getSession();
    theSession.removeAttribute("clientQualInfoModel");
}

/**
 * Return the ClientQualInfoModel corresponding to the session belonging to
 * the given request.
**/
private ClientQualInfoModel getClientQualInfoModel(HttpServletRequest request) throws ServletException {
    HttpSession theSession = request.getSession();
    return (ClientQualInfoModel) theSession.getAttribute("clientQualInfoModel");
}

private void createClientQualConfirmModel(HttpServletRequest request) throws ServletException {
    ClientQualConfirmModel theModel = new ClientQualConfirmModel();
    //
    // Set model properties (not shown here), then add the model to the session
    // so that it is available to the next view.
    // 
    HttpSession theSession = request.getSession();
    theSession.setAttribute("clientQualConfirmModel", theModel);
}

/**
 * Set the model properties using information gleaned from the request.
 * Typically this will be pulling info out of forms and setting properties
 * on the model.
**/
private void updateClientQualInfoModel(HttpServletRequest request) throws ServletException {
    ClientQualInfoModel theModel = getClientQualInfoModel(request);
    
    theModel.getUser().setFirstName(request.getParameter("firstName"));
    theModel.getUser().setLastName(request.getParameter("lastName"));
    //
    // etc.
    //
}

/**
 * Forwards the request and response to the given stateView, which
 * is typically a jsp.  Prior to calling this method, please ensure
 * that all business objects that the stateView depends on are available
 * in the request (either put them in the session or make them available
 * as page scope beans).
**/
private void enterState(String stateView, 
                        HttpServletRequest request, 
                        HttpServletResponse response) throws ServletException {
    RequestDispatcher theDispatcher = ServletContext.getRequestDispatcher(stateView);
    theDispatcher.forward(request, response);
}

Example ClientQualInfo.jsp code fragments

<%!
    //
    // Returns <FONT COLOR="red"> if the given fieldName in the given ClientQualInfoModel
    // is invalid, otherwise returns <FONT COLOR="black">.  Use it like this:
    //
    // <%= fontColorTagFor("firstName", clientQualInfoModel) %>
    // <B> First Name </B>
    // </FONT>
    //
    public String fontColorTagFor(String fieldName, ClientQualInfoModel model) {
        if (model.isFieldValid(fieldName)) {
            return "<FONT COLOR=\"black\">";
        } else {
            return "<FONT COLOR=\"red\">";
        }
    }
%>

<jsp:usebean id="clientQualInfoModel" class="ClientQualInfoModel" scope="page" />
<%
    if (clientQualInfoModel.isInvalid()) {
        //
        // print out the fields that need to be changed
        //
        %>
        <FONT COLOR="red">
        Please review the indicated fields:<BR>
        <jsp:getProperty name="clientQualInfoModel" property="validationErrorList" />
        </FONT>
        <%
    }
%>
<FORM ACTION="ClientQualInfoServlet" METHOD="POST">

<%= fontColorTagFor("firstName", clientQualInfoModel) %>
<B>First Name</B>
</FONT>
<INPUT TYPE="TEXT" NAME="firstName" VALUE="<%= clientQualInfoModel.getFirstName() %>" SIZE="20" MAXLENGTH="40">

<%= fontColorTagFor("lastName", clientQualInfoModel) %>
<B>Last Name</B>
</FONT>
<INPUT TYPE="TEXT" NAME="lastName" VALUE="<%= clientQualInfoModel.getLastName() %>" SIZE="20" MAXLENGTH="40">

<!-- etc. -->

Example ClientQualInfoModel code fragments

public class ClientQualInfoModel extends FormModel {
    //
    // FormModel would be an abstract class that provides
    // functionality used in all form models, such as
    // collecting validation problems.
    //
    private User user;
    private Vector validationProblems = new Vector();

//
// Constructors
//
    public ClientQualInfoModel(User user) {
        this.user = user;
    }

//
// Accessing
//
    public String getFirstName() {
        return this.user.getFirstName();
    }
    
    public String getLastName() {
        return this.user.getLastName();
    }
    
    // etc.

    public boolean isFieldValid(String fieldIdentifier) {
        //
        // This method probably belongs on a superclass.
        // It would use reflection to look for a method
        // called validate<fieldIdentifier>() which returns
        // a boolean.  If no such method exists, assume
        // no validation is necessary and return true.
        // This would probably be a useful method on
        // PersistentObject as well--the validate() method
        // could just use reflection to call all validateXXX()
        // methods on the object.
        //
        ???
    }

    public boolean isInvalid() {
        //
        // The following is the easy case, where the validation
        // of the state is equivalent to making sure that a bunch
        // of business objects are valid.  In some cases (e.g. 
        // multipage forms), business objects may be in a partially
        // valid state but page needs to be validated to continue to
        // the next page.  In these cases one cannot simply validate
        // the business objects, so it is necessary to write some
        // code to do the validation. 
        //
        try {
            this.user.validate();
            return false;
        } catch (ValidationException e) {
            collectValidationProblems(e);
            return true;
        }
    }
}

Notes

Version History

0.3, June 20, 2000.  Added note for checking timed out sessions, fixed a declaration problem in one of the code samples.
0.2, June 19, 2000.  Rewrote lots, added lots.  This should have been the first release.  This release is not organized in cookbook form yet.
0.1, Some time ago, an in-progress version of this document was released in a very incomplete state.