Level: Intermediate Jeff Wilson (wilsonje@us.ibm.com), E-business architect, IBM
01 Jan 2002 JavaServer Pages technology serves a crucial function for Web developers, but many don't leverage its full power. Author Jeff Wilson, e-business architect (and member of the esteemed DragonSlayers team at IBM), shows you how to customize JSP tags to get even more out of this technology. Using the techniques he details in this article, you can add more complex logic to your JSPs, take firmer control of data display, and share data among tags -- all without having to teach your front-end Web developers how to write Java code. The article includes sample tags and tag-handling classes to give you a feel for how it all works.
If you are involved with Web development at all, you're well aware of the fact that Web-based applications today require more dynamically generated content and personalized data than ever before. Architecting a user-friendly interface means that the front-end developer must not only be proficient in the skills of visual design but also in managing and utilizing the flow of content. Custom JSP tags give those front-end developers the means to control how data is processed in back-end Java components without requiring any Java code in the JSP page. In this article, we will be focusing on how custom tags communicate with each other, and how combining them increases reusability and flexibility. We will also walk through a few examples. Brief overview of custom tags
Custom tags are more advanced and flexible than typical JSP tags like <jsp:useBean .../> and <jsp:getProperty .../>. One key benefit that custom tags have over typical JSP tags is that by using custom tags, JSP developers can pass input through by placing data either in tag attributes or between opening and closing tags. Custom JSP tags are composed of three parts:
- The JSP page that the tag is used in
- The tag handler, a Java class that processes the tag
- The tag library descriptor, an XML file that groups tags into a "library" and describes the specifics about each tag, such as its attributes, the tag handle name, short name, and so forth
Custom tags can be configured to run their processes based on the input supplied through a tag's attributes, eliminating the need for embedded Java code within the JSP page. Let's look at an example. Listing 1 shows a shopping cart tag that produces one table row for every product selected by a user:
<table width="100%" border="0">
<user:getUserShoppingList userId="13">
<tr>
<td>
<a href="/servlet/productDataServlet?prodID=$_productId">$_productName</a>
</td>
<td>$_productDescription</td>
</tr>
</user:getUserShoppingList>
</table>
|
In this case, the tag handler class expects both HTML and the user's ID to be passed in as a template for the resulting output (a table row, in this case). The various $_product... references will be replaced by actual values from the products looped through in the tag handler -- the Java class that implements the tag. This demonstrates another key benefit of custom tags: Java programmers don't need to know how to format the data before sending it back to the client. Additionally, as new pages are developed that require data that is similar in content but formatted differently, or as the look of current pages change, the Java components won't need updating.
Controlling presentation logic, not just style
Another very important advantage of custom tags is their ability to communicate with other tags on the same page. With traditional JSP tags, developers can set properties to control the behavior of a JavaBean component, but the bean simply does what it does on its own. By breaking down processes into smaller components, JSP developers can mix and match custom tags to build more complex processes for greater control of dynamic content. Using the output of one custom tag as the input for another increases the tags' reusability. For instance, in our previous example, the user shopping cart tag held all the information about the products purchased. A better design might have the user tag hold only product IDs and let another tag -- a product tag -- manage the product data as needed. Once you separate out the process that collects the details of a product, you have a product tag that can be used with other tags for other purposes. This produces an object-oriented approach to controlling the dynamic data that is retrieved -- and front-end developers can follow this approach without knowing any programming. The code in Listing 2 shows this approach in action. Also, notice that the user's shopping list tag implements another tag, called error:setErrorTemplate. If no products are found in this example, we might want to show an error message. It's easy to see that the two-column table needed for our product list won't be that appropriate for an error message.
<table width="100%" border="0">
<tr class="headerRow">
<td>Product Name</td>
<td>Product Description</td><tr>
</tr>
<user:getUserShoppingList userId="13">
<products:getProductData productId="$_productId"/>
<tr>
<td>
<a href="/servlet/productDataServlet?$_productId">$_productName</a>
</td>
<td>$_productDescription</td>
</tr>
<error:setErrorTemplate>
<tr class="errorRow">
<td colspan="2">You have nothing in your shopping cart...</td>
</tr>
</error:setErrorTemplate>
</user:getUserShoppingList>
</table>
|
The tag handler class can be designed to handle alternate formatting under certain predefined circumstances. This is a good example of how JSP developers can control the logical flow of the data without using if statements or other Java code in the JSP page. With the custom tag, the JSP developer can decide how to determine what gets displayed, not just how it is displayed. In another situation, the same product data tag can be reused with other tags that list products. Notice in Listing 3 that not only does another tag (<products:getProductData category="fitness">) initiate the list, but the output will be formatted differently, because different HTML is passed into the tag.
<table width="100%" border="0">
<products:getProductList category="fitness">
<products:getProductData productId="$_productId"/>
<tr>
<td rowspan="2">
<a href="/servlet/productDataServlet?$_productId"><img
src="$_productImage" border="0"></a></td>
<td>
<a href="/servlet/productDataServlet?$_productId">$_productName</a>
</td>
</tr>
<tr>
<td>$_productDescription: $_productPrice</td>
</tr>
<error:setErrorTemplate>
<tr>
<td colspan="2" class="errorRow">Sorry, no products in this category...</td>
</tr>
</error:setErrorTemplate>
</user:getUserShoppingList>
</table>
|
Methods of tag communication: Benefits and examples
There are a few ways in which custom tags can reference each other and share data. The appropriate method will of course depend on the situation. Nesting tags
A tag is said to be nested when it is completely surrounded by another tag: <outer:tag><inner:tag/></outer:tag> |
No special setup or coding is required to put one tag within another. A tag can be nested in one place and on its own in another. Some tags, of course, will be designed to be nested within others, but nothing is explicitly required to declare a tag as nestable. You can think of HTML table, table row, and table cell tags as nested tags. An example of shared data in table tags is the table's background color (the bgcolor attribute). If the background is set in the table tag <table bgcolor="blue">...</table>, all the rows and cells will be set to blue unless the bgcolor attribute is overridden by an individual tag (for example, <table bgcolor="blue">...<td bgcolor="red">...</td>...</table>). In its most basic implementation, the evaluated inner tag may simply be body input for the outer tag. However, a nested tag might also reference the tags that enclose it (such as parent tags and grandparent tags), allowing linked classes to call each other's methods and properties. In this way, the child and parent tags may share data.
Ancestor tags can be referenced by nesting tags using one of two methods:
-
TagSupport.getParent(): Returns the parent tag; that is, the enclosing tag that immediately surrounds the tag.
-
TagSupport.findAncestorWithClass(from,class): Used when the specific hierarchy of the tags is not known or necessarily preset. The arguments for findAncestorWithClass(from,class) indicate what class to start from and what class to search for, respectively. For instance, in an HTML table tag hierarchy, a table cell tag accessing the table tag would look like this:
TableTag table = (TableTag)findAncestorWithClass(this, TableTag.class); |
If the current tag was not enclosed in a tag whose tag handler was the class specified (TableTag.class, in this case) or if getParent() was called and it had no parent at all, both methods would return null.
Referencing tags with IDs
Another way to share data is to register a class with an ID that can then be retrieved by another tag's handler class. Using this method, a JSP developer cannot simply set an ID in any custom tag if the tag is not specifically programmed to accept it. An ID property is already declared within TagSupport for this very purpose and available to any tag handler class. However, to use the ID property to store the object for other tags to access, two steps must be taken:
- An ID attribute must be specified in the tag library descriptor (the required node can be set to either true or false).
- The tag handler must specifically set itself to an attribute of the
pageContext.
Sharing tag objects with registered IDs may be necessary if the tags cannot easily be nested. Let's refer back to our example of a user's shopping cart. Consider that the <user:getUserShoppingList .../> in Listing 4 below holds various information about a shopping list, including a method that returns a list of product IDs called getProductIds(). Somewhere else in the JSP page, the <products:getProductData> ... </products:getProductData> tag will take a list of product IDs, retrieve product details for each product, and format them before sending them back to the client. The user:getUserShoppingList tag will execute and store itself as an attribute of the pageContext named userShoppingList. The product:getProductData tag will retrieve the value of the attribute and call the method getProductIds() from the getUserShoppingList object.
<user:getUserShoppingList userId="13" id="userShoppingList"/>
...
<products:getProductData productData="userShoppingList">
<!-- Some formatting template -->
...
</products:getProductData>
|
The tag library descriptor for the tag getUserShoppingList might look like Listing 5:
<tag>
<name>getUserShoppingList</name>
<tagclass>com.taglib.UserShoppingListTag</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>userId</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
|
The getShoppingList tag handler would contain the following line once the tag completed all of its processing (perhaps in the doEndTag() method): pageContext.setAttribute(getId(),this); |
getId() retrieves the ID set in the tag (userShoppingList) and this refers to the entire class itself. Once the userShoppingList tag is stored in the pageContext, the getProductData tag handler class can access it using the ID passed into it with the productData attribute. The handler class of the getProductData tag will use that ID (retrieved with the method getProductData()) to find the attribute in the pageContext and cast it back into a UserShoppingListTag object. The code in Listing 6 declares a List called productList and calls the user class method getProductIds() to extract the product list.
UserShoppingListTag userShoppingList =
(UserShoppingListTag)pageContext.getAttribute(getProductData());
List productList = (List)userShoppingList.getProductIds();
|
Referencing tags in the page and session context
Storing an entire tag object can be useful for giving other tags free reign over the properties and methods in it. But you can also choose to expose only parts of the object. In fact, a limitation of the previous example is that the product tag is expecting the product list to come from the UserShoppingListTag object.
Perhaps a better approach is to have the user tag, getShoppingList, simply "export" the product list to the pageContext for any other tag to use. The advantage of this is that other tags won't require prior knowledge of the method that returns product lists, or even knowledge of the object from which the lists originate. If the label for the data stored in the pageContext were provided by the attribute productData, the tag handler for the product tag would look like this:
List productList = (List)pageContext.getAttribute(getProductData());
|
This technique would work for shopping list tags and others, like a tag that calls sale items or products from a given category. If the data were stored in a session, it would like this:
HttpSession session = pageContext.getSession();
List productList = (List)session.getAttribute(getProductData());
|
Walking through example custom tags
In the Download section below, you can download a set of example custom tags. Download the code package and have a look at them. In the final section of this article, I'll use these examples to show you some of the benefits you can achieve by using custom tags. Overview of the example tags
The idea behind the sample tags is fairly simple and straightforward. In total, there are seven custom tags, one JSP file, and one tag library descriptor (a .tld file). The tags work together to display one of three things: a welcome screen, a login screen, or, if an error occurs, a login error message. (The actual login process is not handled by the tags -- we will mimic the process for simplicity's sake by setting session and request variables that would otherwise be set by a servlet or JavaBean component). As Listing 7 shows, there are two main tags: getUserData and nestedLogin. The first gets the user and the second displays the appropriate HTML based on whether or not the user, John Q. Citizen, has logged in. These two tags represent a way that one tag, nestedLogin, can access another tag, getUserData, which was stored in the pageContext. The nestedLogin tag also demonstrates the process of nesting other tags within a tag; it allows other tags to access its methods. The three possible displays are represented by three other tags: isLoggedInHTML, notLoggedInHTML, and logInFailureHTML. These three tags provide access to properties in the nestedLogin tag; the appropriate block of code will be determined and displayed by nestedLogin. The remaining two tags, getUserName and getLoginError, demonstrate two ways of using nested tags: as simple body content and as a means of accessing methods in ancestor tags. Neither of them overrides their ancestor tags; they simply pull data -- namely, the user's name and a login error, if either has been set -- from their ancestors.
<HTML>
<HEAD>
<TITLE>Custom Tag Communication</TITLE>
</HEAD>
<BODY bgcolor="#ffffff">
<!-- LOAD TAG LIBRARY -->
<%@ taglib uri="goforit.tld" prefix="goforit" %>
<!-- SET THE USER -->
<goforit:getUserData id="user"/>
<!-- SET THE LOGIN HTML BASED ON WHETHER OR NOT THE USER IS LOGGED IN -->
<!-- ONE OF THE NESTED NODES WILL BE DISPLAYED ACCORDINGLY -->
<goforit:nestedLogin userDataID="user">
<goforit:isLoggedIn>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS LOGGED IN -->
</goforit:isLoggedIn>
<goforit:notLoggedIn>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THE USER IS NOT LOGGED IN -->
</goforit:notLoggedIn>
<goforit:loginFailure>
<!-- THE HTML IN THIS NODE IS DISPLAYED IF THERE WAS A LOGIN ERROR -->
</goforit:loginFailure>
</goforit:nestedLogin>
</BODY>
</HTML>
|
The tag library descriptor
Listing 8 shows the descriptors for the two main tags. Note the required id attribute in getUserData and userDataID in nestedLogin. These are used to register the user object in the pageContext and to retrieve it in other classes.
<tag>
<name>getUserData</name>
<tagclass>com.taglibrarycommunication.taglib.GetUserDataTag</tagclass>
<info></info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<name>nestedLogin</name>
<tagclass>com.taglibrarycommunication.taglib.NestedLoginTag</tagclass>
<info></info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>userDataID</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
|
The tag handlers
The following sections show some of the important aspects of communication between classes.
GetUserData
This tag handler class retrieves the user data to be used by the other tags.
Note: Because our tags do not handle the login process, these are constructed to look for a logged-in user stored in the session and errors stored in the request. Because we are not actually logging people in, the beginning of this tag mimics the three possible scenarios by setting someone to the session or an error to the request. The code shown in Listing 9 controls the three scenarios -- if both lines below remain commented out, the user is considered not logged in:
// UNCOMMENT TO MIMIC A LOGGED IN USER STORED IN SESSION
//session.setAttribute("user","John Q. Citizen");
// UNCOMMENT TO MIMIC LOGIN ERROR STORED IN REQUEST
//pageContext.getRequest().setAttribute("loginError","Password incorrect");
|
The key to registering the user data is in the doStartTag() method:
public int doStartTag() {
session = pageContext.getSession();
... SET VARIOUS PROPERTIES BASED ON THE USER ...
// THIS IS THE LINE THAT SAVES THIS CLASS TO pageContext
pageContext.setAttribute(id,this);
return SKIP_BODY;
}
|
NestedLoginTag
This class, shown in Listing 11, determines which of three properties are returned to the client, based on whether or not the user is logged in or if there is an error. The values of these three properties are determined by other tags nested within the class in the JSP page. NestedLoginTag determines which tag to display by first pulling in the user registered to the pageContext in the previous tag. It then attempts to retrieve the username and any error that might have occurred. If both are blank, it assumes that the user has not logged in. If the error message is not blank, clearly an error occurred. If the user's name was set, the user was successfully logged in.
// PULL THE userData OUT OF THE pageContext
// WITH THE userDataID SUPPLIED THROUGH THE CUSTOM TAG
GetUserDataTag userData =
(GetUserDataTag) pageContext.getAttribute(getUserDataID());
// SET userName AND loginError FROM VALUES IN userData OBJECT
setUserName(userData.getUserName());
SetLoginError(userData.getLoginError());
...
if (getUserName()!="" &&
getLoginError()==""){
// IF userName IS SET PERSON IS LOGGED IN
pageContext.getOut().print(getIsLoggedInHTML());
} else {
if (getLoginError()=="")
// IF NO userName SET BUT NO loginError SHOW LOGIN
pageContext.getOut().print(getNotLoggedInHTML());
else
// IF loginError SHOW LOGIN AND ERROR
pageContext.getOut().print(getLogInFailureHTML());
}
|
IsLoggedInTag, NotLoggedInTag, and LogInFailureTag
The three tags in Listing 12 are the tags nested within nestedLogin. They all function similarly but set different properties of nestedLogin. Their body content allows the JSP developer to access and set the isLoggedInHTML, notLoggedInHTML, and logInFailureHTML properties of nestedLogin.
// THIS LINE ACCESSES THE PARENT CLASS NestedLoginTag
NestedLoginTag parent = (NestedLoginTag) getParent();
if (parent != null){
BodyContent bc = getBodyContent();
String body = bc.getString();
// SET THE isLoggedInHTML PROPERTY OF THE PARENT CLASS
// WITH THE BODY SUPPLIED THROUGH THE CUSTOM TAG
parent.setIsLoggedInHTML(body);
}
|
GetUserNameTag and GetLoginErrorTag
The tags in Listing 13 simply retrieve the userData object from the pageContext and pull out a property of it.
GetUserDataTag userData =
(GetUserDataTag) pageContext.getAttribute(getUserDataID());
...
if (userData.getUserName() !=null){
pageContext.getOut().print(userData.getUserName());
}
|
Wrapup
JSP pages separate client-side display from server-side logic, and put the power of Java technology at the disposal of Web designers who may not be Java programmers. By using custom tags, you can give more choices to developers working on both tiers of a Web application, and impose an object-oriented approach on Web developers that will encourage the reuse of code modules and tags. Once you've examined the sample tag library included with this article, you should be ready to start using custom tags with your own applications.
Download | Description | Name | Size | Download method |
|---|
| Sample code | j-taglibCommunication.zip | 7 KB | HTTP |
|---|
Resources
About the author  | 
|  |
Jeff Wilson is an e-business architect for the DragonSlayers, a group of consultants, educators, and evangelists within IBM's developer relations division. Contact Jeff at wilsonje@us.ibm.com. |
Rate this page
|