ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Developing Custom Tag Libraries as Tag Files
Pages: 1, 2, 3

Exposing Data to the Calling Page Through Variables

Attributes provide input to a custom action, but sometimes you also need to give the page that contains the custom action access to data produced by the custom action. For instance, the <my:forEvenAndOdd> action is not all that useful unless the page can access the current iteration value in the fragments for even and odd rows. To handle this requirement, data can be passed from a custom action to the caller by exposing it through declared variables.



Example 11-6 shows a version of the tag file from Example 11-5 that's been extended to expose the current iteration value as a variable named current. All differences between the examples are highlighted.

Example 11-6. Exporting data through variables (forEvenAndOdd2.tag)

<%@ tag body-content="empty" %> 
<%@ attribute name="items" rtexprvalue="true" required="true" %> 
<%@ attribute name="even" fragment="true" required="true" %> 
<%@ attribute name="odd" fragment="true" required="true" %> 
<%@ variable name-given="current" variable-class="java.lang.Object" 
scope="NESTED" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<c:forEach items="${items}" varStatus="status" var="current"> 
<c:choose> 
<c:when test="${status.count % 2 == 0}"> 
<jsp:invoke fragment="even" /> 
</c:when> 
<c:otherwise> 
<jsp:invoke fragment="odd" /> 
</c:otherwise> 
</c:choose> 
</c:forEach>  

The variable directive declares the variable. The name-given attribute specifies its name and the variable-class attribute its type. (Here I use the most generic class possible, java.lang.Object, because the collection to iterate over can contain elements of any type.)

The scope attribute accepts one of three values: AT_BEGIN, AT_END, or NESTED. It controls where the caller sees the variable. Despite its name, it has nothing to do with the scopes we've talked about earlier (page, request, session, and application), so visibility would have been a better name for this attribute. If it's set to AT_BEGIN, the variable is visible to the caller immediately after the start tag for the custom action element. If the attribute is set to AT_END, the variable is visible after the end tag. NESTED means it's only visible between the start and end tags.

To make the data visible to the caller, the tag file sets a page scope variable with the name declared by the variable directive. I told you earlier that the tag file has its own page scope, separate from the caller, so the container must do a bit of magic for this to work. For a variable declared as AT_BEGIN or NESTED, it copies the value of the variable in the tag file's page scope to the caller's page scope before invoking a fragment. If the variable is declared as AT_BEGIN or AT_END, it copies the value before exiting the tag file. In the case of a NESTED variable, it also saves and restores the value of the caller's page scoped variable with the same name, if any, before entering and exiting the tag file. Don't worry if this sounds confusing at first; it actually ends up working as you would expect it to.

The tag file in Example 11-6 exposes a variable named current, containing the value of the current iteration value. The local variable is set indirectly with help of the var attribute of the <c:forEach> action. As you may recall, the <c:forEach> action makes the current iteration value available in the page scope variable named by this attribute. By setting the name of the <c:forEach> variable to the name of the declared tag file variable, the variable value set by the <c:forEach> action is also exposed to the caller.

With the new version of the tag file, I can use it to display the current iteration value in each row:

<%@ page contentType="text/html" %> 
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<html> 
<head> 
<title>Even and Odd Rows</title> 
</head> 
<body bgcolor="white"> 
<h1>Even and Odd Rows</h1> 
<table> 
<my:forEvenAndOdd2 items="a,b,c,d,e"> 
<jsp:attribute name="even"> 
<c:set var="counter" value="${counter + 1}" /> 
<tr bgcolor="red"><td>${counter}: Even Row: ${current}</td></tr> 
</jsp:attribute> 
<jsp:attribute name="odd"> 
<c:set var="counter" value="${counter + 1}" /> 
<tr bgcolor="blue"><td>${counter}: Odd Row: ${current}</td></tr> 
</jsp:attribute> 
</my:forEvenAndOdd2> 
</table> 
</body> 
</html>  

Note how the exposed variable is used in EL expressions in both fragments. There's still a problem here: the exposed variable name is hardcoded into the tag file. This may be okay in some cases, but it's better if the variable name can be specified using an attribute, just as you can pick a name with the var attribute for all JSTL actions that expose data. Fortunately, there's a solution, shown in Example 11-7.

Example 11-7. Letting the page author specify the variable name (forEvenAndOdd3.tag)

<%@ tag body-content="empty" %> 
<%@ attribute name="items" rtexprvalue="true" required="true" %> 
<%@ attribute name="var" rtexprvalue="false" required="true" %> 
<%@ attribute name="even" fragment="true" required="true" %> 
<%@ attribute name="odd" fragment="true" required="true" %> 
<%@ variable name-from-attribute="var" alias="current" 
variable-class="java.lang.Object" scope="NESTED" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<c:forEach items="${items}" varStatus="status" var="current"> 
<c:choose> 
<c:when test="${status.count % 2 == 0}"> 
<jsp:invoke fragment="even" /> 
</c:when> 
<c:otherwise> 
<jsp:invoke fragment="odd" /> 
</c:otherwise> 
</c:choose> 
</c:forEach> 

Instead of the name-given attribute used in the previous example, I use the namefrom-attribute and alias attributes of the variable directive in Example 11-7. The name-from-attribute attribute value is the name of the custom action attribute used to name the variable. The named attribute (var in this example) must be declared as required and must not accept a request time value. The alias attribute value declares the name of the tag file's local page scope variable, which the JSP container copies to the caller's page scope as described earlier. The aliasing trick is needed because the page author can assign any name for the variable when she uses the custom action, but a fixed name must be used when developing the tag file. The rest of Example 11-7 is identical to Example 11-6, but I can now specify the variable name in the calling page like this:

<%@ page contentType="text/html" %> 
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<html> 
<head> 
<title>Even and Odd Rows</title> 
</head> 
<body bgcolor="white"> 
<h1>Even and Odd Rows</h1> 
<table> 
<my:forEvenAndOdd3 items="a,b,c,d,e" var="anyName"> 
<jsp:attribute name="even"> 
<c:set var="counter" value="${counter + 1}" /> 
<tr bgcolor="red"><td>${counter}: Even Row: ${anyName}</td></tr> 
</jsp:attribute> 
<jsp:attribute name="odd"> 
<c:set var="counter" value="${counter + 1}" /> 
<tr bgcolor="blue"><td>${counter}: Odd Row: ${anyName}</td></tr> 
</jsp:attribute> 
</my:forEvenAndOdd3> 
</table> 
</body> 
</html>  

Aborting the Page Processing

In Chapter 9, I described how using the <jsp:forward> action or the JSTL <c:redirect> action shifts processing from the current page to the page specified by the page attribute, effectively aborting processing of the current page. Custom actions implemented as Java classes can cause the same thing to happen.

There's no directive or similar mechanism that a tag file can use to explicitly abort processing, but using <jsp:forward>, <c:redirect>, or a custom action that aborts page processing in a tag file has the same effect; both the tag file processing and the processing of the page that invokes the tag file stop after it aborts the processing. You can use this feature to, for instance, develop a smart forwarding action that decides which page to forward to based on runtime conditions, such as the time of the day, the current user, or the type of browser accessing the page.

Packaging Tag Files for Easy Reuse

All examples in this chapter use a taglib directive with a tagdir attribute to specify the directory that contains the tag files. While this is handy for custom actions implemented as tag files in an application you control, it's not as easy as one would want for deployment and use of the tag library in third-party applications. As you may recall from Chapter 7, it's very easy to deploy a tag library packaged as a JAR file; just put the JAR file in the WEB-INF/lib directory and use the default URI as the uri attribute value in the taglib directive.

You can do the same with a tag library developed as tag files, but in this case you must also create a Tag Library Descriptor (TLD) and include it in the JAR file. I described the purpose of the TLD briefly in Chapter 7, but let's take a closer look at it here. Example 11-8 shows the TLD for a tag library with some of the tag files we've developed in this chapter.

Example 11-8. TLD for tag files

<?xml version="1.0" encoding="ISO-8859-1" ?> 
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" 
version="2.0"> 
<tlib-version>1.0</tlib-version> 
<short-name>my</short-name> 
<uri>mytaglib</uri> 
<tag-file> 
<name>copyright</name> 
<path>/META-INF/tags/mytags/copyright.tag</path> 
</tag-file> 
<tag-file> 
<name>forEvenAndOdd</name> 
<path>/META-INF/tags/mytags/forEvenAndOdd.tag</path> 
</tag-file> 
<tag-file> 
<name>htmlFormat</name> 
<path>/META-INF/tags/mytags/htmlFormat.tag</path> 
</tag-file> 
</taglib>  

As you can see, this file is an XML document. Don't worry about the <taglib> element attributes; just copy the element exactly as its shown here into your TLD. I describe it in more detail in Chapter 21, along with the TLD elements not covered here.

The first elements provide information about the tag library itself. The <tlib-version> element contains the version of this tag library and the <short-name> element contains the default namespace prefix for this library. An authoring tool may use the default namespace prefix when it generates the taglib directive and action elements, but a page author can pick different prefixes if needed, as described in Chapter 7. The <uri> element is important. This element declares the default URI (identifier) for the library. The value you use for this element is the value that must be used as the uri attribute value for the taglib directive in the JSP pages to take advantage of the auto-deploy feature, as I described in Chapter 7.

Next comes a <tag-file> element for each tag file. The nested <name> element gives the name for the custom action. It's typically the same as the filename (minus the .tag extension) but you can specify a different name if you want. The <path> element holds the path within the JAR file to the tag file. It must start with /META-INF/tags/. The TLD file itself must also be located in the /META-INF directory in the JAR file, so you need to create a directory structure like this for the tag files and the TLD:

META-INF/ 
mytags.tld 
tags/ 
mytags/ 
copyright.tag 
forEvenAndOdd.tag 
htmlFormat.tag 

Then create the JAR file with the jar command (included with the Java SDK) like this:

C:\> jar cvf mytags.jar META-INF 

This creates a JAR file named mytags.jar containing the contents of the META-INF directory structure. You can now place this JAR file in the WEB-INF/lib directory of any web application that needs the library. When you restart the web container, it will locate the JAR file and its TLD, so that you can identify the tag library in a JSP page with a taglib directive like this:

<%@ taglib prefix="my" uri="mytags" %>

In other words, use the uri attribute with the default URI for the library (declared in the TLD), just as for the JSTL libraries we've used in previous chapters, instead of the tagdir attribute.

You can also use a TLD to identify tag files placed directly in the filesystem, i.e., not packaged in a JAR file. Doing this allows you to use the uri attribute instead of the tagdir attribute for the taglib directive, potentially saving you from changing the taglib directives in a number of JSP pages if you eventually decide to package the tag files in a JAR file for easier reuse in other applications. With this approach, you put the tag files under WEB-INF/tags (or a subdirectory) just as in the first examples in this chapter, but you also place a TLD in the WEB-INF directory (or a subdirectory, like tlds):

WEB-INF/ 
tlds/ 
mytags.tld 
tags/ 
mytags/ 
copyright.tag 
forEvenAndOdd.tag 
htmlFormat.tag 

In this case, the <path> elements in the TLD must specify the context-relative path to the tag files in the filesystem:

<tag-file> 
<name>copyright</name> 
<path>/WEB-INF/tags/mytags/copyright.tag</path> 
</tag-file>  

Other than that, the TLD is the same as in Example 11-7.

Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.


Return to ONJava.com.