Skip to main content

Building Web Components Without a Component Framework

August 4, 2005

{cs.r.title}









Contents
What Is a Web Component?
Two-Phase Request Processing
The Lifecycle of a Standalone Component
Single-State Component
Multiple-State Component
JSP Control
JSP Controls Vs. Portlets
JSP/Servlets Compatibility
Creating JSP Components Using Struts
Struts Dialogs
DialogAction Navigation Rules
Handling Error Messages
Keeping State
What About AJAX?
Conclusion
Live Demos
References and Source Code

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
"http://www.theserverside.com/articles/article.tss?l=RedirectAfterPost">
"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 ( "http://wicket.sourceforge.net">Wicket, "http://www.rubyonrails.com">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.

<br "Figure 1. Standalone login dialog" border="0" height="180" width=
"420" />
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.

<br "Figure 2. Standalone multi-state login component" border="0"
height="255" width="420" />
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.







JSP Control

Figure 3 shows a login component that is redesigned as
part of a larger parent page. Unlike standalone components
that can use any presentation technology, this child control is
based on the JSP specification.

<br "Figure 3. embedded login control" border="0" height="256"
width="416" />
Figure 3. Embedded login control

The JSP control is embedded into the parent page using the
<jsp:include> tag:

[prettify]&lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; 
    language=&quot;java&quot; %&gt;
  ...
  &lt;TABLE&gt;
    ...
    &lt;TR&gt;
      &lt;TD&gt;
        &lt;jsp:include page=&quot;/login.do&quot;/&gt;
      &lt;/TD&gt;
    &lt;/TR&gt;
     ...
  &lt;/TABLE&gt;
[/prettify]

As you can see, the control is dynamically included in the
parent page; there are no additional parameters and no special API.
The control renders itself when the parent page is being
rendered.

As in previous examples, user input is submitted directly to the
component instead of being channeled through a central page
controller. But you may be asking: how can the embedded control
redraw itself after its state changes? How can it force other
components to redraw?

This can be facilitated with one small change in the
request/response cycle: instead of reloading itself after
processing user input, the JSP control reloads the parent page,
which in turn pulls up all embedded components. Figure 4 shows an
example of this approach.

<br "Figure 4. Master page with login control" border="0" height=
"471" width="396" />
Figure 4. Parent page with login control; see the
"http://www.superinterface.com/strutsdialog/logincontrol.html">live
demo

Figure 4 shows a screenshot from a componentized JSP page with
an embedded login dialog. (The page template is borrowed from the
TemplatesBox site.) This
shows how a web page can be built from independent components
without using complex component frameworks or special protocols
like the portlet API. Good old HTTP provides enough to ensure
proper component update and rendering, while the JSP specification
allows including dynamic resources and sharing data between the
components.

Most web frameworks define navigation rules in an external
configuration file, so the child component may not even know that
it was made part of a larger page.

JSP Controls Vs. Portlets

It would be a stretch to compare an API consisting of one
<jsp:include> tag with the feature-rich Portlet
API. Nevertheless, there are similarities.

A portlet, as defined in the "http://developers.sun.com/prodtech/portalserver/reference/techart/jsr168">
portlet specification
, is a "Java technology based web
component, managed by a portlet container, that processes requests
and generates dynamic content." Similarly, a JSP control is a web
component, managed by a JSP container, that processes requests and
generates dynamic content.

The portlet specification states that the "content of a portlet is
normally aggregated with the content of other portlets to form the
portal page." The same can be said about the JSP control.

Users interact with content produced by portlets "by following
links or submitting forms, resulting in portlet actions being
received by the portal, which are forwarded by it to the portlets
targeted by the user's interactions." This unlike the JSP
control, which instead receives form submissions directly; a parent
page may not even know that a request was dispatched to a
control.

The portlet specification shows the following example of portlet
lifecycle, initiated when users access the portal page:

  • The browser makes an HTTP request to the portal.
  • The portal determines if the request contains an action
    targeted to any of the portlets associated with the portal
    page.
  • If there is an action targeted to a portlet, the portal
    requests that the portlet container invoke the portlet to process the
    action.
  • A portal invokes portlets, through the portlet container, to
    obtain content fragments that can be included in the resulting
    portal page.
  • The portal aggregates the output of the portlets in the portal
    page and sends the portal page back to the client.

Now consider the similar lifecycle for a JSP control:

  • The browser submits input data directly to the JSP control.
  • JSP control processes input data, updates state, generates
    error messages if needed, and redirects to parent page.
  • The browser reloads the parent page, which pulls included controls
    using links; a link results in a GET request.
  • When controls receive a GET request, they render themselves, and
    their content fragments are included in the resulting page.
  • The JSP container aggregates the output of the controls in the
    parent page and sends the page back to the client.

Of course, besides visually embedding a component in a web page,
the Portlet API provides many other features, like portlet
preferences, user information, caching, security, access to
persistent storage, packaging, etc. But sometimes visual
aggregation of a component is enough. Returning to the login
control, probably the only information that it needs to
share with other components is that a user is logged in,
and the name of the user.

This information exchange can be facilitated with a single
session attribute, like AUTH, having the user's name
as its value. When this attribute is set, it reports both the fact
that a user is logged in, and his or her name. This can be used,
for example, to show contact information links for registered users
only:

[prettify]&lt;% if (session.getAttribute(&quot;AUTH&quot;) != null) { %&gt;
  &lt;a href=&quot;contact.do&quot;&gt;&lt;img src=&quot;contact.gif&quot;&gt;&lt;/a&gt;
&lt;% } else {%&gt;
  &lt;img src=&quot;blank.gif&quot;&gt;
&lt;% } %&gt;
[/prettify]







JSP/Servlets Compatibility

Using the <jsp:include> directive is a simple and
efficient way to create JSP controls, but there is a catch. The
Java servlet
specification
, version 2.4, section 8.4, reads: "Before the
forward method of the RequestDispatcher interface returns, the
response content must be sent and committed, and closed by the
servlet container." This means that it is not legal to use
forwarding within an included resource. If the container catches us
using server-side forwarding, it has to close the response. As a
result, the portion of the parent page located below the included
component will not be rendered.

The current specification requires us to process input, update
the model, and render output, all at the same place. Basically,
this leaves a developer with two choices: either stick
data-processing code into the included JSP page, or add a bunch of
Writer.println statements into the included servlet.
Neither approach is pretty; no wonder
<jsp:include> is used mostly for simple headers
and footers.

Nevertheless, you can work around this issue. First of all, try
your container. "http://jakarta.apache.org/tomcat">Tomcat, being a reference
JSP implementation, closes responses exactly according to JSP spec,
which does not work well for components that render output by
forwarding to a JSP page. A patch, which keeps the response opened,
is available, but it has not been tested for production
environments.

On the other hand, Resin
works perfectly well. It does not close the response as the spec
mandates, but instead keeps it open until the parent page is
rendered completely. "http://jetty.mortbay.org/jetty">Jetty is somewhere in between;
it can be fooled by using a "proxy" JSP page.

Another option at first seems like a hack, but is quite
reasonable for a stable codebase: using Java classes, automatically
generated by the container from JSP pages. If you use Tomcat, then
all you need is to copy the Java files generated by Jasper from the
Tomcat/work directory into your project directory, and to
compile them together with other project files. Instead of
forwarding to a JSP page, you call the Java code. The code snippet
below uses the class
embedded_0002dchild_0002dlogin_jsp.java.

[prettify]public ActionForward getView(...) {
  HttpSession session = request.getSession();
  HttpJspBase page =
    new embedded_0002dchild_0002dlogin_jsp();
  page.init(this.getServlet().getServletConfig());
  page._jspService(request, response);
  page.destroy();
  return null;
}
[/prettify]

Of course, hacking container code is not the best choice, so I
hope that the JSP spec leads will recognize the need to update the
discrepancies between the servlet and JSP specifications, and will
change the paragraph quoted above to something like this:

"Before the forward method of the RequestDispatcher
interface returns, the response content must be sent and committed,
and closed by the servlet container, if the request was not included
in another request
."

Creating JSP Components Using Struts

Now we can see how a JSP component can be created, using Struts.
Why would one want to create components with "http://struts.apache.org">Struts, when there are
component-oriented frameworks like JSF?

The first reason is to get better control of the
request/response cycle. Struts is a fairly thin wrapper over Java
servlets, and allows you to do quite a lot with the HTTP protocol.
A browser does not care about server technology, but it can use a
good HTTP response.

The second reason is legacy. Why switch to another framework, if
you can reuse the one that has served you well so far? Componentized
Struts allows to use traditional JSP instead of learning another
markup language.

The third reason is pure sport. The "http://java.sun.com/j2ee/javaserverfaces/reference/faqs/#differences">
JSF FAQ
says: "Struts has no notion of server-side components,
which also means that it has no event model for responding to
component events and no facility for saving and restoring component
state." This is true, but can be easily fixed.

Struts is a controller framework and handles requests using
Action and ActionForm classes. An
Action class handles client requests, while an
ActionForm is populated with request data. To promote
ActionForm from a simple transfer object to an
interactive, stateful object, we need to set the
ActionForm scope to session instead of request,
and define behavioral methods in it. If ActionForm
could process client events and store data between requests, it
would look almost like a JSF UI component or a "http://www.opensymphony.com/webwork">WebWork action.

ActionForm cannot accept input events, but
Action can. Therefore, a Struts web component can be
built using a combination of these two classes.

<br "Figure 5. Struts component" border="0" height="540" width=
"450" />
Figure 5. Struts component

Figure 5 shows how the phases break down:

  • Input phase: 1) Populate the ActionForm with request
    values; 2) dispatch the input event to the handler method in
    Action; process the input, update the component state,
    and generate error messages if needed.
  • Component reload: Redirect to the same action or to the parent
    page.
  • Render phase: 3) Forward to the JSP page, which reads data from
    the form bean.

The only class from the core Struts library that comes close to
component development is DispatchAction. It allows
the processing of submit events using handler methods. But
DispatchAction does not help to keep server state,
store component properties, or handle error messages.

Struts Dialogs

This is where the "http://struts.sourceforge.net/strutsdialogs">Struts Dialogs
library can be employed.

(Note: The Struts Dialogs library is not affiliated with, or endorsed
by, Struts or Apache.)

The workhorse of the Struts Dialogs library is the
DialogAction class. It extends
DispatchAction in several ways:

  • It works uniformly with pushbuttons and with image buttons.
  • It handles initialization events.
  • It buffers error messages.
  • It stores component state (in a corresponding action form).
  • It renders the view according to component state.
DialogAction defines several default keys, used for
event dispatching. Among them is a default event prefix,
DIALOG-EVENT; a mapping name for component reload,
DIALOG-RELOAD; and a mapping name for default view,
DIALOG-VIEW. The last is used by single-page dialogs.







DialogAction Navigation Rules

When an HTML form is submitted, DialogAction
dispatches an event to a corresponding handler method. Automatic
validation must be turned off in a corresponding form bean. The
handler validates input data explicitly, and performs a model
update if needed. In case of an error, the handler saves errors in
the session and redirects to the same action, effectively reloading
the action. When DialogAction receives a "clean" GET
request, it renders a view.

This is how a lifecycle is defined for a standalone login dialog
in struts-config.xml:

[prettify]&lt;action path = &quot;/login&quot;
  ...
  &lt;forward name = &quot;DIALOG-RELOAD&quot;
           path = &quot;/login.do&quot; redirect = &quot;true&quot;/&gt;
  &lt;forward name = &quot;DIALOG-VIEW&quot;
           path = &quot;/loginform.jsp&quot;/&gt;
  &lt;forward name = &quot;LOGIN-SUCCESS&quot;
           path = &quot;/userhome.do&quot; redirect = &quot;true&quot;/&gt;

&lt;/action&gt;
[/prettify]

This is the same lifecycle for an embedded login control:

[prettify]&lt;action path = &quot;/login&quot;
  &lt;forward name = &quot;DIALOG-RELOAD&quot; 
           path = &quot;/parentpage.do&quot; redirect = &quot;true&quot;/&gt;
  &lt;forward name = &quot;DIALOG-VIEW&quot; 
           path = &quot;/loginform-embedded.jsp&quot;/&gt;
  &lt;forward name = &quot;LOGIN-SUCCESS&quot; 
           path = &quot;/parentpage.do&quot; redirect = &quot;true&quot;/&gt;
&lt;/action&gt;
[/prettify]

Instead of reloading itself, an embedded control reloads the
parent page, which automatically reloads all controls. The same
Java code is used for both standalone and embedded components.

Handling Error Messages

Error messages are generated by a method that handles input
events. Messages must be saved between requests, so the handler
method stores messages in the session object. After the component
reloads, the JSP page reads messages from the session.

DialogAction does not explicitly remove messages
from the session after the page has been rendered. Instead,
messages are cleared each time a new input event is processed.
Struts 1.2.6 introduced a new feature in
RequestProcessor, which removes messages from the
session after they have been accessed. In this case, messages will not be
redisplayed when the page is reloaded manually.

Keeping State

For simple dialogs, there is no need to define explicit state.
All that is needed is to store input/output data between requests,
which is facilitated by using session scope for an
ActionForm. For multi-state components, state either
has to be defined explicitly, or has to be calculated based on some
criteria.

DialogAction does not define a companion
ActionForm or specific component states, but some of its
subclasses do. For example, CRUDAction (which
implements create, edit, view, and delete operations for a business
object) requires ActionForm to implement the
ICRUDForm interface. WizardAction, which
builds web wizards, requires ActionForm to implement
the IWizardManager interface.

What About AJAX?

Two-phase request processing appears to be less important with
the increasing acceptance of AJAX. But the idea of rendering a
component whenever it is reloaded is still important, because some
AJAX components do not expect that a parent page can be refreshed
by a user.

Also, building a component for both AJAX and non-AJAX
applications is easier when the non-AJAX application uses two-phase
request processing. Therefore, two-phase request processing seems
to be a valuable technology for web applications.

Conclusion

This article explained how to create simple stateful components
and embedded page controls, using existing JSP technology.
Components can be created fully in JSP, or with aid of your
favorite web framework. I used Struts because I know it well, but
you may try the one of your choice. Even if a framework has a
different component architecture, like Wicket, it is usually
possible to include a JSP fragment in a resulting page. This opens
exciting possibilities of seamless integration of JSP components
with existing and new web frameworks.

Live Demos

The following demos are built with the Struts framework and
the Struts Dialogs library:

The samples are run on a development server, which can go
offline from time to time.

References and Source Code

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 >> Servlets   |   Web Design   |