Java.net article: Almost Portlets

Last year I wrote an article about building web components using the proven Java Server Pages (JSP) technology. I came up with a viable architecture, but it had two major downsides: it depended on the Struts framework and required a patch when running on the popular Tomcat server.

Today I am presenting a pure JSP library that is compatible with all standard JSP 1.2+ containers and that allows the creation of independent portlet-like page fragments like login forms, checkout wizards, tabbed notebooks, or image sliders. The fragments handle their own input and render themselves without having any knowledge about composite page into which they are aggregated. Similarly, the composite page knows nothing about the lifecycle of page fragments.

JavaScript and/or XMLHTTPRequest is not required for fragments to function properly. On the other hand, if XMLHTTPRequest is available, page fragments switch to asynchronous mode (Ajax) automatically and update themselves in place, without full page reload.

Building a Composite JSP Page with the Include Action

JSP technology provides several ways of putting together a composite page out of multiple atomic subviews. One of the possible options is using the <jsp:include> action to include a dynamic web resource:

<jsp:include page="myfragment.jsp"/>

When a composite page is being rendered, an included JSP fragment is compiled and executed, and its output is incorporated into content of the composite page (Figure 1):

Read-only fragment
Figure 1. Read-only fragment

The dynamic inclusion is used primarily for standard page building blocks like headers or footers. Web frameworks usually treat JSP as a presentation technology that has only one phase, the render phase; therefore, included JSP fragments are rarely interactive.

Multiphase Lifecycle in JSP

Anyone who has created JSP-based web applications knows that, in fact, JSP supports an input phase in addition to the render phase. After all, users of pure JSP applications somehow manage to submit data to the server. JSP makes input data accessible through the HttpRequest object. Input values are not automatically converted, validated, and propagated to server-side data objects. Third-party libraries like Apache BeanUtils help accomplish these tasks.

The fact that JSP is tightly built around the HTTP protocol makes the following smart trick possible. Consider a fragment that renders an HTML form. The action attribute of the form refers to the name of the JSP fragment itself. When the form is submitted, its data is sent to the location of the included fragment, not to the address of the composite page. Now the JSP fragment not only renders itself but also handles its own input (Figure 2). Voila, a JSP component has been created.

Interactive fragment
Figure 2. Interactive fragment

The major hassle in the above approach is updating the composite page after the form has been submitted to the JSP fragment. A fragment must know the location of the composite page to redisplay it. Manually keeping track of the parent address would be tedious.

JSP Controls Tag Library

The JSP Controls Tag Library presented in this article was created to manage the details of target and reload locations for JSP components and to update components in place whenever possible. In terms of the library, a JSP component is a stateful server-side object that is defined in a separate JSP file, accepts user input, and render itself according to its state.

The library manages the lifecycle of JSP components in a five-step process, supporting both input and render phases (Figure 3):

  1. The cycle begins with the initial page load, starting the render phase.
  2. When the JSP processor encounters a <jsp:include> action in a composite page, it generates an HTTP request to obtain the content of the included component.
  3. The component chooses a view relevant to its state and renders it. After all of the components have rendered themselves, the render phase finishes and a composite page is presented to a user.
  4. The user initiates the input phase by submitting an HTML form or by activating a command link. The browser sends input data directly to a component. The component processes data and updates its state if needed.
  5. After input data has been processed, the component redirects to location of the composite page, effectively switching from input phase back to render phase. Steps 1 through 3 are repeated, and an updated page is presented to the user.

JSP component lifecycle (standard mode)
Figure 3. JSP component lifecycle (standard mode)

If the browser has JavaScript turned on and the XMLHTTPRequest object is available, the library switches to Ajax mode by making an asynchronous request to update the component without full page refresh, so steps 5, 1, and 2 are skipped, and step 4 jumps right to step 3. The incremental update is more effective in regards to network traffic and avoids the complexities of identifying the reload address.

A JSP component incorporated into a page looks and behaves uniformly whether it runs in Ajax mode or not. In fact, a browser that renders a web page in an off-screen buffer may deliver the same flicker-free experience in non-Ajax mode as other browsers do in Ajax mode. The dual-mode functionality of JSP components is invaluable for environments where JavaScript is not allowed or in browsers that do not support the XMLHTTPRequest object, like some mobile browsers.

The Skeleton of a JSP Component

A component is defined in a separate JSP file and must be enclosed in a <jc:component> element that has unique ID. The file name is used as the target of a submit request; the component ID is used to facilitate in-place update of component markup in a composite page.

The component lifecycle is managed with the Handler, Reload, Prerender, and optional Render custom tags. Tag order is important: one (or more) Handler tags comes first, then the Reload tag, followed by a Prerender tag. Render tags, if any, are specified last. Shown below is the skeleton of a login component that has two states, two corresponding subviews, and two events to process:

<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="jc" uri="http://jspcontrols.net/tags/core" %>
<jc:component id="LoginComponent">

  <%-- Input phase --%>
  <jc:handler event="loginEvent"> ... </jc:handler>
  <jc:handler event="logoutEvent"> ... </jc:handler>
  <jc:reload/>

  <%-- Render phase --%>
  <jc:prerender> ... </jc:prerender>
  <jc:render view="notloggedin"> ... </jc:render>
  <jc:render view="loggedin"> ... </jc:render>

</jc:component>

JSP components are event-driven. An event is an arbitrary request parameter that is recognized by a Handler tag. It makes sense to use suffixes like ".event" or prefixes like "Event:" to make sure that events do not got mixed with regular request parameters. A Handler tag evaluates its body if the request contains a parameter that matches the tag's event attribute.

If a Handler tag responds to an event, the input phase, also known as postback, is initiated. Unlike some frameworks, where postback is triggered by the POST request only, the input phase in the JSP Controls Tag Library can be triggered by any request that contains an event known to a component. "Eventless" requests submitted with the POST method are processed by a Handler tag with no attributes. After an event has been processed, a Handler tag sets the "input phase" flag in the page scope.

The Reload tag looks for the "input phase" flag. If the flag is set, the Reload tag terminates further processing of the fragment and reloads the composite page, thus switching from input phase to render phase. When the composite page reloads, it initiates a GET request that loads the fragment. This request comes clean of any event parameter, so any Handler and Reload tags fall through, and the remainder of a fragment renders a view.

Ajax mode does not require reloading a composite page. After an event has been handled, the Reload tag falls through and the component renders a view immediately. To distinguish between an Ajax request and a regular one, the Reload tag looks for a special ajax parameter in the request. This parameter is added automatically by a JavaScript function.

The Prerender tag performs URL housekeeping and patches the response header in Ajax mode. This tag is mandatory. Its body is a convenient place to check component state and to select a view to be displayed by one of the Render tags.

The Render tag selects a component subview by its name, abstracting a subview from the component state. In practice, subviews are often selected with conditional JSTL tags that evaluate component state directly. The following sample renders a logout form for a logged-in user and handles the logout event:

<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="jc" uri="http://jspcontrols.net/tags/core" %>
<jc:component id="LoginComponent">

  <%-- Input phase --%>
  <jc:handler event="loginEvent"> ... </jc:handler>
  <jc:handler event="logoutEvent"> 
    <font color=
"darkgreen"><c:remove var="USER" scope="session" /></font>
  </jc:handler>
  <jc:reload/>

  <%-- Render phase --%>
  <jc:prerender/>
  <c:if test='${empty USER}'> ... </c:if>
  <c:if test='${not empty USER}'>
    <font color=
"darkgreen"><form action="${jspcComponentAddress}">
      <p>Current user: ${USER.name}</p>
      <input type="submit" name="logoutEvent" value="Log out"/>
    </form></font>
  </c:if>

</jc:component>

In the sample code shown above, the user information is displayed if the user-account object exists in the session. When the Log Out button is activated, the logout event is sent to the component. The second Handler tag processes the event and logs out the user. The render phase that will follow will display a login form, because user is now logged out. Notice the scoped variable jspcComponentAddress that provides the full address to the component. As a simpler alternative, a component file name like logincomponent.jsp can be used directly.

The Composite Page

The composite page embeds JSP components using the <jsp:include> action and can contain other markup as well. The composite page does not have to conform to a component lifecycle, but it should not generate cumulative effects on reload:

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
 <head><script src="jspcontrols.js"></script></head>
  <body>
    ...
    <div id="LoginComponent">
      <jsp:include page="loginComponent.jsp"/>
    </div>
    ...
  </body>
</html>

The <div> element around <jsp:include> action must have an ID that matches the ID of the JSP component. This makes it possible to find the spot in the composite page where component markup will be inserted in Ajax mode.

A Peek Behind the Scenes

Aside from the control tags discussed above, the library contains several optional tags. These tags may help to define an HTML form, an input element, and a command hyperlink. Consider the command link defined inside the sample.jsp fragment:

<jc:link event="showdetailsEvent">Show Details</a>

It generates the following HTML markup:

<a href="sample.jsp?showdetailsEvent" 
   class="jspcCommand">Show Details</a>

The link contains the name of component to activate and the event as a request parameter. The generated markup is very simple, so in most cases regular HTML tags can be applied directly. In non-Ajax mode, the link triggers a regular synchronous request. The Ajax-related code is hooked up at runtime using a piece of smart JavaScript code called Behaviour.

In case you did not know, after an HTML page is loaded into the browser, it is parsed and converted into an object structure called a DOM tree that represents all elements of the original textual HTML page. Behaviour allows modification of DOM elements using an associative array that employs CSS selectors as keys. Assigning event handlers to appropriate events, like onclick for a hyperlink or onsubmit for a form, is a breeze with Behaviour. The command link shown above is augmented at runtime using the following rule:

var rules = {
  ...
  'a.jspcCommand' : function(element){
    element.onclick = function(){
      JSPControls.loadComponent(this);
    }
  },
  ...
};

The above rule looks for all A elements that have the CSS class jspcCommand, and hooks up the JSPControls.loadComponent function to their onclick events. The result of this operation is identical to the following markup:

<a href="sample.jsp?showdetailsEvent"
   onclick="JSPControls.loadComponent(this);">Show Details</a>

When a link is activated by a user, the JSPControls.loadComponent function sends an asynchronous request to the server, receives updated markup, and inserts it into the composite page, without a full page reload.

By assigning behavior to DOM elements dynamically using CSS selectors, HTML markup is kept tidy. JSP files are not polluted with custom tags that produce loads of JavaScript code, as regular HTML tags can be used instead. Custom tags are often used to hide the complexity of HTML and JavaScript from a server-side Java developer. Behaviour provides a more elegant approach than custom tags.

If anything goes wrong during a call to a JavaScript function, if JavaScript is not supported by a browser or is turned off, or if the XMLHttpObject is not available, then the browser sends a traditional synchronous request using the address specified in the link. The fallback mechanism ensures graceful degradation with no noticeable effect on user experience besides a longer page update due to reloading the full page.

State Management

Since JSP components use redirection in non-Ajax mode, they must store their state on the server between requests, usually in the session object. Even if a component is stateless from the business point of view, session is used behind the scenes to temporarily save the component's address and the address of the composite page.

In Ajax mode, the composite page is not reloaded. The input and render phases of a component are executed during one request cycle; therefore, one might be tempted to reuse input data from the request and not to save it in the session. The joy ends when a user hits Refresh button: after a full page reload the component, state is lost. Therefore, saving state in the session is recommended for Ajax mode as well. This practice agrees with the Java EE Blue Prints' JavaScript recommendations for Ajax component writers: "Store state that spans pages on the server as a Java object scoped to the HttpSession. The client and server objects should be synched on page refreshes and page loads."

Saving state on the server works perfectly for both non-Ajax and Ajax modes. The downside is losing state information after session times out. Session should not be used as a long-term storage of important business data. Instead, it should hold current user interface settings, like the current tab in a tab component, or the current image in an image slider, or the current page in a table.

Integrating with an Action Framework

JSP components are supposed to be used as independent page fragments, like a login portlet, calendar, search box, or tabbed notebook. Just grab a component template, modify it if needed, and include it into a composite page. This scenario works well for most pure JSP applications, but action frameworks like Struts may have issues with this simple pattern.

In a Struts application, a page is often addressed from different locations, such as initial rendering from one action and redisplay after submitting a form from another action. It is recommended you provide an explicit reload location using a special parameter of JSP component. From the Struts application point of view, activating an embedded JSP component looks like an explicit page refresh:

<jsp:include page="loginComponent.jsp">
  <jsp:param name="jspcReloadAddress" value="/setup.do" />
</jsp:include>

Another possibility of integrating with an action framework is pushing business-related code out of the JSP fragment. For example, replacing the handler code with Struts action is as simple as one <jsp:include> tag:

<jc:handler event="login">
  <jsp:include page="LoginAction.do"/>
</jc:handler>

Instead of returning an ActionForward object, an action class must return null; the view is selected and rendered by the JSP component. This seems like a perfect marriage: a command-type framework like Struts or WebWork handles input and output of data, providing important services like automatic parsing of request parameters, type conversion, validation, and propagation to server-side objects, while the JSP Controls Tag Library orchestrates incoming events and renders component content.

Ready-To-Use Components

JSP Controls Tag Library started out as a JSP component management library. To verify the viability of design principles, a tabbed notebook fragment (Figure 4) was created and worked amazingly well.

Tabbed Notebook
Figure 4. Tabbed notebook

Like all other JSP components, a tabbed notebook must be defined in a separate JSP file. And, like other components, it works in Ajax mode as well as in standard mode, so it can be included in an existing JSP page and remain fully functional under Netscape 4. To simplify designing a tabbed notebook, two special tags were created: TabControl and TabPage. There is no need to manage component states and views explicitly; everything is encapsulated into the tags:

<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="jc" uri="http://jspcontrols.net/tags/core" %>
<jc:component id="MyTabComponent">
  <jc:tabcontrol>
    <jc:tabpage label="Tab1">
      <p>Tab1 content goes here.</p>
    </jc:tabpage>
    <jc:tabpage label="Tab2">
      <p>Tab2 content goes here.</p>
    </jc:tabpage>
  </jc:tabcontrol>
</jc:component>

Hopefully, users will start creating their own component templates.

Summary

The JSP Controls Tag Library shows that it is not necessary to switch to a component web framework to develop and use web components. Creating componentized event-driven applications with a sensible approach to Ajax is totally possible with good old JSP technology. Bulletproof backward compatibility with older browsers comes as a free gift.

Live Demos

The project website contains online demos and samples of several JSP components. The site is built using JSP technology, so it seamlessly integrates JSP components to showcase their features and ease of use. Of course, all components are dual-mode and work properly with JavaScript turned off.

References

Michael Jouravlev lives in California and has a degree in computer science from the Moscow Aviation Institute in Moscow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值