JSF is touted to be the ultimate component framework for Java
web application programming. Tapestry claims to be based on the
idea of component development. And across enemy lines, ASP.NET
generated a whole new market for web components. What are web
components and can they be developed with something more
traditional like JSP and Struts?
What Is a Web Component?
A web component can be defined as a stateful server-side
object that handles client events and input data, renders itself
according to its state, and has a certain lifecycle. A web
component can be embedded into another component, can contain other
components, or can be standalone. Embedded components are often
called controls, especially by the ASP.NET crowd.
Depending on the particular component architecture, the
parent/child association can relate either to server objects or to
visual representation. For example, JSF differentiates between a
server-side component and a corresponding page widget, while
Tapestry combines both component template and a Java class in one
definition.
This article treats a web component in the old-fashioned way, as
a resource that is identified with a unique location, accepts user input from request parameters, and is able to
render itself.
What is new in that, you may wonder? Nothing but good old HTTP
with a twist. If you use JSP as the presentation layer for your web
applications, this article may open some new possibilities.
Two-Phase Request Processing
The central concept of the discussed approach is the splitting
of one submit request into two phases: input and output. This
two-phase request processing solves several issues related
to the submission of HTML forms, like implicit double submits, annoying
POSTDATA messages, and unfriendly user experience for reload, back,
and forward buttons. The two-phase request/response sequence looks
like this:
The browser submits input data to a component.
The component processes the input, updates its state, and redirects
the browser to the result location.
The browser loads a result page using another request.
Because this pattern includes redirection, it can also be called
"Redirect-After-Post" or "Post/Redirect/Get." It must be
understood that redirection is an inherent part of this pattern.
Redirection allows splitting one solid "submit input data, respond
with result page" process into two phases, which are completely
independent from the server standpoint. This technique is
implemented in several frameworks either as the main vehicle for
component lifecycle handling (Wicket, Ruby on Rails), or as an option
(JSF).
Two-phase request processing has three modes of operation:
With an initialization event, a component is prepared
for use with new set of data.
In the input phase, a component accepts input data,
updates the domain model, and generates error messages.
In the view rendering phase, a component generates a
result page.
The Lifecycle of a Standalone Component
A standalone component has a whole page for itself, so the
address of the web page is the same as the location of a component.
When a HTML form is submitted, the component handles the event,
validates input data, and updates its state.
If there's an error, the component saves error data in the
session and redirects to itself. This is called component
reloading. Redirection to the same location does not cause an
infinite loop, because the new request is of the GET type and is "clean;"
that is, it does not contain query parameters. When the component
receives a "clean" GET request, it renders itself.
If no errors were detected, the component can either relinquish
control to another component, or can reload itself and render a
page corresponding to its new state. Redirection is a preferred way
of passing control to prevent implicit resubmits.
Stateful web components must be explicitly initialized using a
special request parameter. Otherwise, it would be impossible to
distinguish between the following situations:
Using the component with a fresh set of data.
Navigating to component using a link.
Manually reloading a component using the browser's refresh
button.
Automatically reloading a component as the second phase of
request processing.
The initialization event offers a chance to reset and clean the
component, and to fill it with new data. Three other events--linking, manual refreshing, and automatic reloading--are treated in
the uniform fashion: the component receives a "clean" GET request
and renders a view corresponding to current state. This means that
navigating to the standalone component via a link displays the
current state of the component, but does not initialize it.
Single-State Component
Figure 1 shows the a simplest case of a standalone
component, which has only one state and one view. Such a component
can be called a dialog. This particular dialog is used for
user login. It collects the user name and password and validates
them. If the login information is valid, it redirects to the user's
home page. If invalid, the dialog generates an error message,
temporarily stores it in the session, and reloads itself. After
reloading, the dialog redisplays the invalid user name and password
along with an error message. The cycle repeats until a user enters
valid login information.
Figure 1. Standalone login dialog
The login dialog is a stateful object; it saves the user name and
password between login attempts. On the other hand, the login
dialog does not define an explicit state name, because there are no
other states in which the login dialog can exist.
The login dialog is represented on a web page as an HTML form.
When the form is rendered, its input fields are populated with
dialog properties. When the form is submitted, the request sends
form data directly to the login dialog.
It may appear that the dialog does not have to be initialized
explicitly: each time it is navigated to, it can be recreated. This
is not true. A dialog handles a request in two phases, so it is
impossible to distinguish the reload phase from navigating to it
using a web link. In both cases, the dialog is accessed with a GET
request, so there should be something different between the
requests that sets apart initialization and reload operations.
Therefore, all two-phase components, even simple ones, must be
explicitly initialized. If the login page is the first page of a website, it will be initialized at creation time. Otherwise, a link to
the login dialog should contain an initialization parameter.
Multiple-State Component
Figure 2 shows a version of the standalone login component,
which not only allows the user to log in, but also tracks the user's login
status. This component has two different states: "not logged in"
and "logged in," and two corresponding views.
Figure 2. Standalone multi-state login component
Whenever a logged-in user navigates to the login component
location, the component displays user information and allows the
user to log out.