How to Create Web Pages With Forms (Java Developer)
Draft 0.3
June 29, 2000
Ted Lee (tlee@startups.com)
1. Applicability
This is a cookbook on how to create web pages with forms from the perspective
of the Java developer. Typically there will be at least two people
involved in building a form--a web developer and a Java programmer.
Read this document when any of the following are true:
-
You are a Java developer working on a page or pages containing forms.
-
You are working with a Web developer who is responsible for the page appearance
and you know who it is.
-
You have a spec or some idea of what the pages are supposed to do.
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 Agree On a State Model Of the Web Site
Before you start creating a page, you will ideally have a spec of what
the pages are supposed to do. This might be a verbal description
or a more formal set of documents. The first thing you should do
is to find, discover, or create a state model of the set of web
pages you intend to build. The state model does the following:
-
It describes which pages exist, and helps name the pages.
-
It describes the potential flows between pages.
-
It serves as a written agreement between the web developer and the Java
developer on what they are jointly building.
It doesn't matter who creates the state model--it could be you, it could
be the information architect who designed the spec, it could be the Java
developer. The important thing is that everybody agrees on the same
state model, since all the coding that everybody does will be affected
by it. The following supersimplified example shows how you might
create a state model. Imagine that the following images are a spec
for a series of web pages to be built:
"We want a page with a form where we can enter the name of an existing
user in a textfield and a legal structure from a pulldown. We will
retrieve the legal structure pulldown entries from the database (we can't
just put them into html). It should look something like this."

"Also, if the username is invalid and the user submits, it should come
back to this page and highlight Username in red, along with a message that
says what the problem was."

"When the username is really valid, hitting submit should take them to
a success page which shows the userID and the email address of that user."

The first thing to do is to name all the pages. Try to pick
something which is descriptive of the user task, or the state that the
user is in when they are looking at the page. For example, you might
pick:
-
EnteringUserName
-
EnteringUserNameError
-
Success
Then draw a state diagram showing the possible transitions between the
pages:

The balloons indicate states, and the arrows indicate transitions between
states. Note that the EnteringUserNameError state has a
state transition that leads back to the same state--if the username isn't
valid, it's still an error. Please make sure that everyone (you,
the Web developer, the spec writer) agree on the state model!
2.2 Create A Model and A Servlet For Each State
Each state in your state model will generally be represented by three components:
a model, a jsp, and a servlet. Each component has a single responsibility:
Model - contains the dynamic information used by the jsp at runtime
to build the page.
JSP -
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:
-
Make JSPs as HTML-like as possible for web designers--avoid large scriptlets
and numerous bean property settings
-
Keep presentation in JSPs as much as possible
-
Reduce dependencies on single files--we don't want web designers and java
programmers touching the same files simultaneously or we get version clashes
-
Break down and simplify conditional logic so Java programmers can easily
maintain code
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
-
JSP author and servlet author would have to agree on form input names (e.g.
"firstName"). We probably want to have a naming convention related
to the business objects. If there is an accessor method on a User
object called "getFirstName()" we probably should use "firstName" as the
input name.
-
Validation should be done on business objects, not solely in the UI or
servlets. The data access layer should do validation on persistent
objects prior to committing them to the database, since invalid objects
should never be stored there.
-
Maybe we need a ClientQualState business object as a facade for the jsp
views. Otherwise the jsps need to potentially read a lot of stuff
out of the session. Such an object also gives a good place to put
things like validation errors.
-
Do we want to have naming conventions for jsps, servlets, facade objects?
Probably yes, something like <state>.jsp, <state>Servlet, <state>Model
would be make it easier to tell which pieces are the view, controller,
and model for each discrete state. For example, ClientQualInfo.jsp,
ClientQualInfoServlet, ClientQualInfoModel.
-
In the above example, ClientQualInfo.jsp represents at least two states:
the standard entry state, and the error state. We could break this
down further and make the error page a separate state and avoid some of
the conditional code. The tradeoff is that we'd have two jsps that
are largely similar that would both have to be changed should there be
presentation changes.
-
For multi-page forms, we potentially need a model for each page.
If the models themselves relied on the same persistent objects (e.g. User,
with each page partially filling in the User object), the above simple
definition of isInvalid() would not work. There would need to be
separate validation for each page.
-
Having the controller create the model for the next state is a bit weird.
I suppose we could omit this step and just have the jsp do it in the jsp:usebean
tag.
-
Probably need something in servlet to check for timed out sessions.
In other words, servlet can't assume there is a valid session. Probably
belongs in servlet superclass. It would be inconvenient if we had
to check this every time we accessed a session variable.
if (! request.isRequestedSessionIdValid()) {
enterState("login.jsp", request, response);
}
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.