Advanced Views
Until now, we have treated the view as a black box, assuming that a single JSP page will convert our model data into HTML to send to the user. In reality, this is a tall order. Large JSP pages with lots of embedded code are just as unwieldy as a large front controller. Like the controller, we would like to break the view into a generic framework with the specialized pieces separated out.
The challenge is to break up the view within the restricted programming model of JSP. Remember that one of our goals was to minimize embedding code in our JSP pages, since it blurs the line between the view and the controller. Thus, we will avoid the kinds of classes and interfaces we used to solve the same problem in the controller.
Fortunately, JSP gives us a different set of tools to work with: JSP directives and custom tags. We will use both of these extensively in the next two patterns to help separate the view into reusable components.
The View Helper Pattern
One mechanism for reducing specialization in views is a view helper . A view helper acts as an intermediary between the model and the view. It reads specific business data and translates it, sometimes directly into HTML, and sometimes into an intermediate data model. Instead of the view containing specialized code to deal with a particular model, the view includes more generic calls to the helper. Figure 4-4 shows how a view uses helpers.
View helpers increase reusability in two ways: by reducing the amount of specialized code in a view, helpers make views more reusable; and, since a helper encapsulates a specific kind of interaction with the model, helpers can be reused themselves.
Implementing a View Helper
When you think about view helpers in JSP, custom tags should immediately pop to mind. Conceptually, custom tags fit the bill—they adapt Java objects into JSP markup. Moving code embedded in JSP into custom tag classes reduces coupling, since the tag defines a clear interface independent of the underlying objects. And since tags are grouped into libraries by function, they are inherently quite reusable themselves. While it is easy to think of all custom tags (or even all tags) as view helpers, they are not the same thing. A view helper is a tag, or set of tags, that translates model data into a convenient form for the view.
A view helper may read business data in many forms, including JavaBeans, Enterprise JavaBeans, direct database access, or access to remote web services. For our example, let’s look at the last of these: accessing a remote web service.
Really Simple Syndication (RSS)[5] is an XML format that is the de facto standard for web sites to exchange headline information. RSS files are generally available via public HTTP from most news sites, at the very least. Example 4-6 shows a slightly simplified RSS file for a made-up news page, PatternsNews. (For simplicity, we have stuck to the 0.91 version of RSS. The current version, 2.0, is far more complicated.)
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN" "http://www.scripting.com/dtd/rss-0_91.dtd"> <rss version="0.91"> <channel> <title>Patterns news</title> <link>http://www.patternsnews.com</link> <item> <title>Local pattern-creators honored</title> <link>http://www.patternsnews.com/stories?id=0001</link> </item> <item> <title>Patterns solve business problem</title> <link>http://www.patternsnews.com/stories?id=0002</link> </item> <item> <title>New patterns discovered!</title> <link>http://www.patternsnews.com/stories?id=0003</link> </item> </channel> </rss>
We would like to read RSS files and include their headlines on our own web site. In order to do this, we will build a view helper that makes the RSS data available as a JSP custom tag. Figure 4-5 shows the pieces we will build.
Parsing the RSS
For starters, we need to parse the RSS into a Java format that we can
use. To do this, we create an RSSInfo
class that
parses RSS files and stores them as Java objects. We
won’t go into the specifics of how the parsing
works, since the innards look a lot like the DOM-based XML parser we
built for the last example. Example 4-7 shows the
interface to the RSSInfo
class.
public interface RSSInfo { // channel information public String getChannelTitle( ); public String getChannelLink( ); // item information public String getTitleAt(int index); public String getLinkAt(int index); public int getItemCount( ); // parse the RSS file at the given URL public void parse(String url) throws Exception; }
The RSSInfo
object represents an intermediate data
structure: it is not exactly the underlying data, but also not
exactly what we are using in the application. Building an
intermediate data model may seem inefficient, but it can help
significantly in reusability. Since the RSSInfo
class is independent of any type of display mechanism, it can be used
in many contexts: JSP custom tags, servlets, web services, etc. If
the parsing was implemented directly as custom JSP tags, the logic
would have to be duplicated.
Using the RSS: Custom tags
Now that we have a generic method for parsing RSS, we would like to use it in a JSP
environment. We could store the RSSInfo
object in
the session and access it directly using JSP directives. While this
option is quite flexible, it embeds a fair amount of logic for
iteration and such in the JSP.[6] This logic would
have to be rewritten for each page that used the RSS parser. As
another option, we could create a custom tag that read the RSS and
returned a preformatted table. This method has the advantage of being
easy, since only one line of JSP is needed to include all the
headlines. Unfortunately, it would mean including our page styles in
the custom tag, and we would be unable to reuse the tag on a
different page.
The best solution in this case is a hybrid: we will design custom tags to parse and iterate through the RSS data, and expose JSP scripting variables with the relevant values. This solution allows us the flexibility to format the output any way we want, while performing the heavy lifting in the custom tag logic. To format the RSS data as a table, for example, we would like to have our JSP look something like Example 4-8.
<%@page contentType="text/html"%> <%@ taglib prefix="rss" uri="/ReadRSS" %> <html> <head><title>RSS Results</title></head> <body> <rss:RSSChannel URL="http://www.patternsnews.com/patternsnews.xml"> <b><a href="<%= channelLink %>"><%= channelName %></a></b> <br> <table> <rss:RSSItems> <tr><td><a href="<%= itemLink %>"><%= itemName %></a></td></tr> </rss:RSSItems> </table> </rss:RSSChannel> </body> </html>
Here, our view helper consists of two custom tags. The first,
RSSChannel
, takes a URL corresponding to an RSS
file and downloads and parses that file using the
RSSInfo
class. It exposes the channel title and
channel link information in two scripting variables:
channelTitle
and channelLink
.
The second tag, RSSItems
, performs a similar task,
repeating its body for each item in turn and exposing the
itemTitle
and itemLink
variables. The helper (the tags) is therefore responsible for
creating and interpreting the intermediate data model (the
RSSInfo
).
The RSSChannel
tag source is shown in Example 4-9.
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class RSSChannelTag extends BodyTagSupport {
// scripting variable names
private static final String NAME_ATTR = "channelName";
private static final String LINK_ATTR = "channelLink";
// the input parameter
private String url;
// the RSS parser
private RSSInfo rssInfo;
public RSSChannelTag( ) {
rssInfo = new RSSInfoImpl( );
}
// called with the URL parameter from the tag
public void setURL(String url) {
this.url = url;
}
// used by the RSSItemsTag
protected RSSInfo getRSSInfo( ) {
return rssInfo;
}
// parse the RSS and set up the scripting variables
public int doStartTag( ) throws JspException {
try {
rssInfo.parse(url);
pageContext.setAttribute(NAME_ATTR, rssInfo.getChannelTitle( ));
pageContext.setAttribute(LINK_ATTR, rssInfo.getChannelLink( ));
} catch (Exception ex) {
throw new JspException("Unable to parse " + url, ex);
}
return Tag.EVAL_BODY_INCLUDE;
}
}
The RSSItems
tag is slightly more complicated, since it
implements the IterationTag
interface. It loops
through the item data, setting the itemTitle
and
itemLink
variables with every pass.
RSSItems
also uses the
findAncestorWithClass( )
method to locate the
RSSInfo
object that was stored by the parent. The
RSSItemsTag
source is shown in Example 4-10.
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class RSSItemsTag extends BodyTagSupport implements IterationTag {
// the names of the scripting variables
private static final String NAME_ATTR = "itemName";
private static final String LINK_ATTR = "itemLink";
// keep track of looping
private int counter;
// the stored RSS data, obtained from enclosing tag
private RSSInfo rssInfo;
public RSSItemsTag( ) {
super( );
counter = 0;
}
// find the RSSInfo from the enclosing tag and set the
// initial values of the scripting variables
public int doStartTag( ) throws JspException {
if (rssInfo == null) {
RSSChannelTag rct =
(RSSChannelTag)findAncestorWithClass(this, RSSChannelTag.class);
rssInfo = rct.getRSSInfo( );
}
pageContext.setAttribute(NAME_ATTR, rssInfo.getTitleAt(counter));
pageContext.setAttribute(LINK_ATTR, rssInfo.getLinkAt(counter));
return Tag.EVAL_BODY_INCLUDE;
}
// after each pass, increment the counter if there are still items left
// refresh the scripting variables at each pass
public int doAfterBody( ) throws JspException {
if (++counter >= rssInfo.getItemCount( )) {
return IterationTag.SKIP_BODY;
} else {
pageContext.setAttribute(NAME_ATTR, rssInfo.getTitleAt(counter));
pageContext.setAttribute(LINK_ATTR, rssInfo.getLinkAt(counter));
return IterationTag.EVAL_BODY_AGAIN;
}
}
}
With the custom tags, we have built a reusable view helper. The
helper encapsulates a general mechanism for RSS parsing in an
intermediate class, RSSInfo
, and interprets the
data using JSP custom tags. We could add other helpers, such as those
to use the RSSInfo
object in a web service. View
helpers add a slight overhead to request processing. But the
benefits, in terms of extensibility and reuse, tend to far outweigh
these costs. By encapsulating common functionality, view helpers
provide a simple way to organize otherwise complicated data access.
We see an analogous pattern from the business object perspective when
we look at the Session Façade pattern in Chapter 9.
Composite View
Views tend to have a lot of repeated elements. This isn’t a bad thing—a good user interface requires consistency. If the general presentation or the navigation were different from screen to screen within an application, the user would be very confused indeed.
Think about the structure of web sites. Many sites follow a basic pattern, like the one shown in Figure 4-6: the content of the page is surrounded by a header on the top (usually with ads), a navigation area on the left, and a footer on the bottom. This consistent layout makes navigation within a site easy, and allows people unfamiliar with a new site to understand it quickly.
Ideally, we would like the page’s code to reflect this high-level organization. We should be able to specify the structure of the page in one generic template, and then vary the content on a per-application and per-page basis. Further, we should be able to apply this concept recursively, allowing each of the areas in our page to separate themselves into template and content. The Composite View pattern supports both of these requirements.
The Composite View pattern
The Composite View pattern is based on the GoF composite pattern. The idea is fairly simple: treat the objects as a tree structure, in which parents expose the same interface as their children. When this concept is applied to a view, it means thinking of each page as composed of a number of elements. Each of the elements may be a leaf, which displays an actual widget to the screen. An element may also be a container, which contains multiple elements as children. The children of a container may be leaves, or other containers, resulting in the tree-like structure. Figure 4-7 shows the participants in a Composite View.
The Composite View pattern is a staple of most graphical systems. In Java’s Swing, for example, the user interface is built as a set of panels. Each panel contains any number of components, as well as other panels.
View
represents a common interface implemented by
both leaves and containers. There are varying opinions on how rich
this interface needs to be. Obviously, it must contain methods like
draw( )
, which are required by both leaves and
containers. Methods that are specific to containers, like adding and
removing children, could go either way. We could treat a leaf as a
container with no children, thus allowing containers to export
exactly the same interface as the leaves they contain (like in the
Decorator pattern). Or we could put the container-specific interfaces
only in the composite classes, saving overhead. In a system like
Java, where runtime type-checking is relatively cheap, the container
methods are usually restricted to the container, but either choice is
valid.
The LeafView
and
CompositeView
objects implement the
View
interface. Leaves always display directly to
the output. A composite must coordinate the display of all its
subcomponents, as well as any rendering it might do itself.
In some systems, the ordering of subcomponents within a composite is not important, but in displays it usually is. In a graphical application, there’s frequently some concept of “layout”—a richer syntax for how components are arranged within a container. The layout might include the sizes and shapes of various components, for example. To be truly flexible, our solution needs to abstract layout within a container from the components themselves, a feat accomplished with a template.
A template allows us to specify the high-level layout of a page, based on a generic set of components. The layout can be reused on different sets of components, allowing the view to vary dynamically. By modifying the template, we can make changes to the global layout in one central place. And templates may themselves contain other templates and components.
In the same way that the Service to Worker pattern divided the controller into reusable dispatchers and actions, the Composite View pattern divides the view into reusable components. These components contain clear interfaces, so they can be shared, substituted, and extended at will.
Implementing composite views
The view tier in a web application has built-in support for a basic
level of composition. Both JSPs and Servlets are used as views, but
they can also be containers. The JSP include
directive and the matching RequestDispatcher.include(
)
method for servlets allow for the embedding of any other
JSP page, servlet, or even static web page.
While the servlet include mechanism is quite flexible, the JSP include directive is not. Including a page requires embedding its URL into the parent page, creating a tight coupling between the parent and the child. We want to avoid this coupling, so that we can reuse the parent page on different sets of children.
In this example, we build the JSP custom tags to support the notion
of containers. One tag defines a container and one defines an element
in a container. The container
tag determines the
name of the container and and the container maps that label at the
back end to support the include tag. The include
tag works like the normal JSP include
tag, except
that instead of specifying a URL, we specify a label, and the
container maps that label to different URLs depending on the page
being viewed. An include
tag is always nested
within a container
tag. Example 4-11 shows how a container might be used to create
a layout similar to that in Figure 4-6.
<%@page contentType="text/html"%> <%@ taglib uri="/container" prefix="cv" %> <cv:container name="main"> <html> <head><title><cv:include label="title" /></title></head> <body> <table width="100%" height="100%"> <tr><td><cv:include label="header" /></td></tr> <tr><td><cv:include label="body" /></td></tr> <tr><td><cv:include label="footer" /></td></tr> </table> </body> </html> </cv:container>
We populate the template with an XML file. It maps the “title,” “header,” “body,” and “footer” labels to different URLs, depending on what screen is being shown. To do this, we will create an XML document similar to the one we used for workflows. Example 4-12 shows how this format could be used for the “main” container.
<?xml version="1.0" encoding="UTF-8"?> <views> <view name="page1" template="MainTemplate.jsp"> <container name="main"> <include label="header" url="Header.html" /> <include label="footer" url="Footer.html" /> <include label="body" url="page1.html" /> </container> </view> <view name="page2" template="MainTemplate.jsp"> <container name="main"> <include label="header" url="Header.html" /> <include label="footer" url="Footer.html" /> <include label="body" url="page2.html" /> </container> </view> </views>
We now have a view
element for each page in the
application that specifies the template to be used. Each view
consists of one or more container
elements. The
name
attribute of a container matches the
name
attribute specified in the
container
tag in the JSP. While this might seem
overly complicated, it has the advantage of supporting nesting. As
long as container names are unique within a given page, as many
containers as desired can be put into a single view.
Tip
When including form elements in an HTML page, it is often necessary to supply a name to each element. This name must be unique within the page to allow proper processing when the request is received. If form elements are nested in a composite view, there is a potential to use the same form element name multiple times.
In this example, each page lists MainTemplate.jsp as its view, but specifies
different values for the “body”
include. When the cv:include
tag is encountered in
the JSP above, it substitutes the appropriate URL from the XML file.
Reusing the front controller and dispatcher
To support this at the backend, we will extend our earlier Service to
Worker example. We will use the same front controller class but
create a new dispatcher, the CompositeDispatcher
.
The composite dispatcher parses the XML file and stores view
information in a View
object. Example 4-13 shows the View
object’s interface.
public interface View { // get the url for a given container and label public String getURL(String container, String label); // check if there is a record for the given container public boolean hasContainer(String container); // get the template this view uses public String getTemplate( ); }
Once again, we won’t go into the messy details of
storing View
information or parsing XML. Once the
XML is parsed, the CompositeDispatcher
stores each
view indexed by name in a HashMap
called
“views.” When a request comes in,
it expects the page name in a request attribute. The dispatcher
simply looks up the appropriate view
object and
stores it in the request so that the container tags can use it. The
getNextPage( )
method of this dispatcher looks
like this:
public String getNextPage(HttpServletRequest req, ServletContext context) { String page = (String)req.getParameter(PAGE_ATTR); View v = (View)views.get(page); if (v == null) { v = (View)views.get(DEFAULT_VIEW_NAME); } req.setAttribute(VIEW_ATTR, v); return (v.getTemplate( )); }
Building the custom tags
Once again, JSP
custom tags prove to be a powerful and efficient mechanism for
extending the view. In this case, using tags in combination with a
dispatcher simplifies them even further. Our first tag, the
ContainerTag
, simply looks up the appropriate container
in the View
object provided by the dispatcher. If
the container exists, it is stored for use by the
ContainerIncludeTag
. If it does not, the entire
contents of the container are skipped. Example 4-14
shows the ContainerTag
source.
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import javax.servlet.*; import java.io.*; public class ContainerTag extends TagSupport { // the session key for the view object private static final String VIEW_ATTR = "view"; // the name of the container private String name; // the view object, for use by ContainerIncludeTag private View view; // determine if the named view exists public int doStartTag( ) throws JspException { view = (View)pageContext.getRequest( ).getAttribute(VIEW_ATTR); if (!view.hasContainer(name)) return SKIP_BODY; return EVAL_BODY_INCLUDE; } // get the stored view public View getView( ) { return view; } // used by the JSP tag to set the name public void setName(String value) { name = value; } // get the name of this container public String getName( ) { return name; } }
The ContainerIncludeTag
simply uses the stored
View
object to map the include parameters into
URIs. It then uses the JSP-provided pageContext
object to include the view’s content. Example 4-15 shows the
ContainerIncludeTag
.
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import javax.servlet.*; import java.io.*; public class ContainerIncludeTag extends TagSupport { // the label of this include private String label; // get the view object from the parent tag // map the given name to a URL and include it public int doEndTag( ) throws JspException { // find the parent tag ContainerTag ct = (ContainerTag)findAncestorWithClass(this, ContainerTag.class); View v = ct.getView( ); // get the view URL String viewURL = v.getURL(ct.getName( ), label); if (viewURL != null) { try { // include it pageContext.include(viewURL); } catch( Exception ex ) { throw new JspException("Unable to include " + viewURL, ex); } } return EVAL_PAGE; } // used to set the name from the JSP tag public void setLabel(String value) { label = value; } }
Using templates
Just like in the Service to Worker example, the XML file format and implementation we have shown here is simplified. There are lots of possible extensions to this basic concept that can make templates a more convenient and powerful tool. One simple extension is to allow values to be substituted directly from XML attributes rather than from a file. For instance, in order to add titles to our web page in the previous example, we would rather not include a whole file with just the title in it. Instead, in the Views.xml file, we specify something like:
<include label="title" text="Page 1" direct="true" />
The <cv:include>
tag reads the
direct
attribute and substitutes the string
"Page 1
" instead of including an entire page.
When developing a web application, using composite views promotes consistency in the user interface and reusability for views. As with decorators, composite views can cause trouble when they are deeply nested or have too many dependencies.
In this chapter, we looked at three patterns that promote reuse in the presentation tier. All three use the same divide-and-conquer strategy, separating large, specialized components into smaller, resuable pieces. The Service to Worker pattern encapsulates model changes in reusable actions and navigation in a replaceable dispatcher. The View Helper pattern shows how to add reduce specialization in views by delegating tasks to a reusable helper, and the Composite View pattern divides the view into reusable containers and leaves.
[5] RSS is an actively evolving standard. For a good introduction and history, see http://backend.userland.com/rss.
[6] Version 2.0 of the JSP spec adds an expression language and standard tag library, which greatly reduce the need for embedded scripts in these situations. This does not, however, provide an excuse for manipulating ill-suited business objects directly in JSP.
Get J2EE Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.