ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Designing Tapestry Mega-Components

by Howard Ship
11/21/2001

Tapestry: Java Web Components is a powerful, flexible, open source framework for creating Web applications in Java. Tapestry builds on existing J2EE technologies to create an environment for robust, scalable, and highly dynamic Web applications. The framework was designed from the start to allow the creation of rich, reusable components. This article will explore the construction of one such component, the Palette, which exercises many of the advanced features of Tapestry. It assumes a good deal of familiarity with Tapestry; if you haven't been exposed to Tapestry yet, the Tapestry Home Page is a good place to start.

The majority of components in Tapestry directly correspond to HTML elements. For example, the Form component generates a <form> element, and the Image component generates an <img> tag. There are components for input fields and links. Some components break this mold, such as the PropertySelection component, which generates a <select> and all the <option> elements within it. Then there are "mega-components," which generate a whole slew of HTML code, often with additional JavaScript to support the user experience.

The Palette is one such mega-component. It facilitates the selection of multiple items from a list. The interface consists of two columns of values and a pair of buttons. The left column is the list of available items that may be selected. The right column is the list of items actually selected. The "select" button moves one or more items from the available list to the selected list. The "deselect" button moves items out of the selected list and back into the available list. Figure 1 shows the Palette component in action.

Screen shot.
Figure 1. "Transfer Books" from Virtual Library

Here, the Palette is part of the Virtual Library application; it is being used to transfer the ownership of books from one person to another. The left column contains all the books owned by one person; the right column is the list of books to transfer to the other person. In between the two columns are the buttons for moving books back and forth between the two columns.

The Palette component includes other functionality. If desired, the two lists can be kept sorted, either by the item's label, visible to the user, or by the item's value (which is hidden from the user, but meaningful to the application). Finally, the component can be configured to allow manual sorting; in this mode, two additional buttons, "move up" and "move down," appear, allowing the user to change the order of items in the selected list.

The buttons are only active when appropriate; for example, the "select" button is disabled unless the user has selected one or more items in the available list. When disabled, the buttons take on a "dimmed" or greyed-out appearance.

All and all, this is quite a bit of functionality. A Web developer wishing to include similar behavior on a static Web page, or even a JavaServer Page, is facing quite a task. First, JavaScript functions for manipulating the list of options within the select lists would have to be created. This includes moving items between the two lists, as well as keeping the two lists in correct sorted order (this is further complicated by the differences in the programming models between the two major browsers, Netscape Navigator and Microsoft Internet Explorer). If manual sorting is desired, then additional functions for moving items up and down within the selected list are required.

Further event handling functions are needed to respond when the user presses any of the buttons. Another aspect of event handling is to enable and disable the buttons as appropriate. This includes updating the image used for each button to reflect its enabled or disabled status.

These functions will typically be put in a <script> block that is placed between the and <body> tags. Lastly, the developer will create the HTML for the various links, images and <select> tags (the actual lists of available and selected items). This includes defining names for the various elements so that they can be referenced from the JavaScript functions, as well as attaching event handlers to the elements. All told, this adds up to several hundred lines of HTML and JavaScript.

This same functionality could be implemented in a number of ways, using simpler HTML interfaces. In many Web applications, a single <select> component is used for multiple selection. Although this behavior is directly supported by the Web browser, it has a number of limitations. A special modifier key must be used in conjunction with the mouse to perform multiple selections, and that modifier varies between browsers and platforms. In addition, for long lists (where the scrolling of the control is required), it is very difficult to see what exactly is selected, and easy to mistakenly lose the selection by clicking and item without pressing the modifier key.

Another option is to create a table of checkboxes and labels. This is not particularly elegant and can consume a large amount of screen space. Unlike the Palette component, a list of checkboxes requires that the user visually inspect the page to determine which items are currently selected. Neither of these simpler interfaces allows for the ordering of the selections.

Tapestry reverses those limitations. Creating the Palette component itself was involved; it is by far the most complex component yet created for Tapestry. Basic development took two days. Later, an additional day was spent reworking it to operate reasonably in Netscape Navigator 4, and then more time to adjust to Navigator 6. But now that the component exists, it is easier to use the Palette component than it would be to implement the other solutions. That is a telling demonstration of the power of using components.

Futher, there are no arbitrary limitations on using a Palette component. A developer may use as many Palettes on a page as desired, with multiple Palettes in a single form, or spread between forms. A Palette component is represented by a single tag in its containing component's HTML template (the containing component is typically a Tapestry page, but like all Tapestry components, a Palette may be used to construct even larger, more complex components). Typically, a <span> tag is used:

<span jwcid="palette"/>

Tapestry's HTML template parser allows any HTML tag to be used to represent a Tapestry component, simply by including the jwcid attribute.

So, from this single tag, the Palette component must generate all of the following:

To pull this off, the Palette component relies on the Tapestry framework, which was designed to facilitate complex components such as the Palette.

Related Reading

Java Cookbook: Solutions and Examples for Java DevelopersJava Cookbook: Solutions and Examples for Java Developers
By Ian Darwin
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

IRequestCycle

One of the central interfaces in Tapestry is the request cycle object, IRequestCycle. This object represents the current request invoked by the client Web browser. Every component has access to this object; it is passed to each component as one parameter of the render() method, and it is also available as a property of the containing page.

The request cycle allows components to find other pages within the application, or to identify which page will generate the response for the current request. It also allows components to locate each other during the rendering process.

The request cycle contains a collection of named attributes (this is similar to how data is stored inside an HttpSession). Certain components provide services to the components they wrap (the components that are, directly or indirectly, contained within the open and close tags of the component). Service-providing components can register themselves into the request cycle using a well-known name, before rendering the components they wrap. Wrapped components can find the service-providing component when their render() method is invoked. This is shown in Figure 2.

Diagram.
Figure 2. Body sequence.

Thus the Palette component finds the Form and Body components because Form and Body have registered themselves into the request cycle as part of their render() method.

Body Component

The majority of the DHTML support in Tapestry is provided by the Body component. The Body component replaces the <body> tag in most Tapestry pages. The Body component provides a number of services to all the Tapestry components it wraps:

The Palette component leverages all three of these services. It preloads the images for the Select, Deselect, Move-up and Move-down buttons (in both their enabled and disabled states). It includes a large amount of JavaScript for each Palette component in the form of event handling functions for the buttons, supported by additional utility functions. The Palette component uses the initialization support to assign event handler functions to the various controls within the Palette.

Tapestry Client-Side Scripting

The Tapestry framework includes a powerful mechanism for dynamically generating JavaScript from a template. The main feature of Tapestry scripting is how the input script is parameterized to produce the output JavaScript. The parameterization includes a combination of symbol replacement, conditional blocks, and even looping operations.

In effect, a Tapestry script is an object that takes as input a collection of symbols and produces, as output, the desired JavaScript. Along the way, it will create new symbols that are communicated back to the caller.

For the Palette component, the associated script takes as input the names assigned to the form and to the Palette component, some flags used to indicate how items are to be sorted (by label, by value, or manually), and the URLs for the various images used on the buttons.

Tapestry script documents have three main sections. In the first section, new symbols are defined, using the <let> directive. The second section is the <body> directive, where functions and variables are created. The final section, <initialization>, contains JavaScript code to be executed only after the overall HTML page loads.

Tapestry script documents contain additional directives to accomplish conditional blocks, looping, and inserting symbols.

The Palette script starts by creating a new symbol, baseName, which combines the name of the form and the name of the Palette component together. All other functions and objects incorporate this base name, which provide a reasonable guarantee that there will not be any name collisions (regardless of the number of forms or Palette components on the page).

<let key="baseName">
  <insert property-path="formName"/>_<insert property-path="name"/>
</let>

For example, the select button will be associated with a particular JavaScript function, executed when the user clicks the button. Two new symbols are created:

<let key="selectFunctionName">
  select_<insert property-path="baseName"/>
</let>

<let key="selectOnClickScript">
  javascript:<insert property-path="selectFunctionName"/>();
</let>

The first symbol sets the name of the JavaScript function to be executed when the select button is clicked. The second will be used as the href attribute of the HTML <a> tag for the select button, when that portion of the page's HTML is generated. Now that the name of the function is known, the function itself can be created (within the <body> section of the Tapestry script):

function <insert property-path="selectFunctionName"/>()
{
   if (<insert property-path="selectDisabled"/>)
    return;
    
    var source = <insert property-path="availablePath"/>;
    var target = <insert property-path="selectedPath"/>;
    
    palette_transfer_selections(source, target);
  
<if property-path="sortLabel">
      palette_sort_by_label(target);
</if>
<if property-path="sortValue">
      palette_sort_by_value(target);
</if>
  <insert property-path="updateFunctionName"/>();
}

Again, this function is parameterized (remember that the name of every aspect of the JavaScript must be dynamically named to prevent any naming collisions within the client Web browser). This code simply checks whether the button is disabled (due to a lack of selected options in the available list). If enabled, the selected options are transferred to the selected list. Following that, the selected list is sorted (if required). Finally, the Palette's update function is invoked; its job is to enable and disable the various buttons based on which items are selected.

At runtime, this portion of the script will generate the following JavaScript:

function select_Form0_inputColor()
{
   if (buttons_Form0_inputColor.selectDisabled)
     return;
    
   var source = document.Form0.inputColor_avail;
     var target = document.Form0.inputColor;
    
     palette_transfer_selections(source, target);
  

     palette_sort_by_label(target);


   update_Form0_inputColor();
}

This shows how the form name, Form0, and the Palette name, inputColor, have been incorporated into the names of the functions and HTML elements.

HTML Template

Like most Tapestry components, the Palette uses an HTML template. The template contains static HTML, which is passed directly back to the client Web browser. Certain portions, marked with tags (including the jwcid attribute), are placeholders for other components.

<table jwcid="table">
  <tr>
    <th>Available</th>
    <td class="controls" rowspan=2>
      <a jwcid="selectButton"><img jwcid="selectImage" alt="[Select]"/></a>
      <br>
      <a jwcid="deselectButton"><img jwcid="deselectImage" alt="[Deselect]"/></a>
    </td>
    <th>Selected</th>
  </tr>
  <tr>
    <td><select jwcid="availableSelect"/></td>
    <td><select jwcid="selectedSelect"/></td>
  </tr>
</table>

For clarity of discussion, the actual HTML template used has been simplified here. Regardless, the template shows that the component produces a table with two buttons and two <select> elements. Everything else, the names of each element, the JavaScript, the event handlers, the URLs of the images used, is provided dynamically. Defining all of this dynamic behavior is split between the Palette component's specification file, and its Java code.

For example, the specification for the select button, the button used to move items from the available list to the selected list, is built using two components:

  <component id="selectButton" type="Any">
    <static-binding name="element">a</static-binding>
    <binding name="href" property-path="symbols.selectOnClickScript"/>
  </component>

  <component id="selectImage" type="Image">
    <binding name="image" property-path="selectImage"/>
    <binding name="name" property-path="symbols.selectImageName"/>
  </component>

The first <component> element identifies the selectButton component as an Any component, a type of component that can produce any kind of HTML element. The element binding identifies that the Any is configured to produce an <a> tag. This may seem redundant of the HTML template (where it was also shown to be an <a> tag), but the HTML parser used with Tapestry component templates delibrately ignores the actual tag used -- it is only interested in the structure of the template. The fact that an <a> tag was used is a convienience for the HTML producer, who'll be able to preview the HTML in a standard editor like HomeSite.

In addition, the href attribute of the Any tag is set to the value of a symbol output from the script -- the selectOnClickScript property discussed earilier. The path, symbols.selectOnClickScript, means that the symbols property is obtained from the Palette component, and, within it, the property selectOnClickScript is extracted. The Palette class is responsible for providing access to the symbol's Map as a read-only JavaBeans property.

Wrapped inside the selectButton is the selectImage component, of type Image. This component generates an HTML <img> tag. The Palette component provides another property, selectImage, which identifies the image to be used with the button. The Palette component includes defaults for all the images it needs (for the different buttons and states), but allows any of the images to be overriden, using additional component parameters. The getSelectImage() method checks for a selectImage parameter, or provides the default image if the parameter is unspecified.

The client Web browser will see the following HTML for this portion of the Palette:

<a href="javascript:select_Form0_inputColor();"><img src="/private-assets/net/sf/tapestry/contrib/palette/select_right.gif" border="0" name="selectimage_Form0_inputColor" alt="[Select]"></a>

This same pattern -- defining names of elements and functions, implementing the functions, and linking the HTML elements to those functions -- is followed for all of the other aspects of the Palette component.

Conclusion

Tapestry demonstrates the possibilities for easily assembling complex user interfaces from reusable components. The facilities provided by the framework allow for substantial complexity, easily weaving together both client- and server-side logic with dynamically-generated HTML. Meanwhile, the open source community surrounding Tapestry continues to grow, which means that there will be a bumper crop of these kinds of sophisticated components going forward, a prospect which should delight any Java Web developer.

Howard Ship is an open source Java-based Web developers on several projects, including Tapestry.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.