|
Developer: J2EE
Developing JSTL-like Tags with JSP 2.0
by Andrei Cioroianu
Learn how to use the Simple Tags API and build custom tags that evaluate JSP expressions, control the flow in a JSP page, and create Java Collections.
JavaServer Pages (JSP) and JSP Standard Tag Library (JSTL) provide many useful tags (also known as actions) for Web developers. In addition, JSP 2.0 has two APIs, called Classic Tags API and Simple Tags API, that allow you to build custom tags/actions. The former API was inherited from JSP 1.x and is used by JSTL for historical reasons. (JSTL 1.0 was developed before JSP 2.0, and JSTL 1.1 hasn't been ported to the new API.) JSTL also doesn't take advantage of the JSP Fragments and Dynamic Attributes, which are new JSP features. This article uses the new API and features of JSP 2.0 to build custom tags that complement JSTL. It provides an API overview and shows you how to develop
- Tags that export variables
- Conditional tags
- Iteration tags
- Tags with dynamic attributes
- Cooperating tags
Simple Tags API Overview
When you use a custom tag in a JSP page, the JSP container of your application server translates <prefix:customTag> ... </prefix:customTag> into Java code that calls the methods of a class named tag handler. Therefore, if you want to develop your own custom tag, you have to provide a tag handler class, which must use either the JSP 1.x Classic Tags API or the JSP 2.0 Simple Tags API. If you compare these two APIs, you'll notice that the new API is much easier to use. The Simple Tags API has a single interface (javax.servlet.jsp.tagext.SimpleTag) that defines the methods for handling custom tags. These methods are usually called from the Java Servlets that the JSP container generates automatically from your JSP pages.
The javax.servlet.jsp.tagext.SimpleTagSupport class implements the SimpleTag interface so that you have to code only the doTag() method if your tag handler extends SimpleTagSupport. The following steps describe how to develop a simple tag handler:
Step 1: Design the Custom Tag
First, you must choose a name for your tag and figure out its attributes. Then you create a Tag Library Descriptor (TLD) file, which uses an XML format defined by the JSP specification, to tell the JSP container how to handle and validate your custom tag. This article presents a sample TLD file named util.tld.
Step 2: Create a Tag Handler Class
You have to provide a Java class that implements the SimpleTag interface. The easiest way is to extend SimpleTagSupport or one of its subclasses. The VarTagSupport, IfTag, and WhileTag classes in this article extend SimpleTagSupport. The other tag handler examples extend VarTagSupport.
If you want to use attributes that aren't specified in the TLD file, your tag handler must implement the javax.servlet.jsp.tagext.DynamicAttributes interface as in the MapTag example discussed in the Tags with Dynamic Attributes section.
Step 3: Initialize the Tag Handler Instances
Every tag handler class must have a public constructor with no arguments where you can place your initialization code. Some of the tag handler classes in this article (EvalTag, ListTag, and MapTag) have a public no-arg constructor that initializes the instance variables with default values. Other classes (IfTag, WhileTag, and ItemTag) have no constructors. Note that the Java compiler automatically generates a public no-arg constructor that does nothing if the class doesn't have any constructors.
Step 4: Provide Attribute Setters
The values of the tag attributes from the JSP pages are communicated to the tag handler through setAttribute() methods. For example, the <u:eval> tag in this article has four attributes: var, scope, expr, and type. The EvalTag handler class implements the setExpr() and setType() methods and inherits setVar() and setScope() from VarTagSupport.
Dynamic attributes are communicated through the setDynamicAttribute() method, defined by the DynamicAttributes interface.
Step 5: Implement the doTag() Method
This is where you implement the custom tag's logic. The doTag() method is called by the JSP container after all attribute setters. You can use getJspContext() here to obtain a javax.servlet.jsp.JspContext object that provides access to the JSP environment. You can call getJspBody(), which returns an instance of javax.servlet.jsp.tagext.JspFragment, which represents the JSP body placed between <prefix:customTag> and </prefix:customTag>. You can also use the getParent() and findAncestorWithClass() methods if you want to develop tags that cooperate like <u:list> and <u:item>, which the last section of this article discusses.
Step 6: Test the Custom Tag
The JSP pages that use the custom tag must import its tag library with the <%@taglib%> directive. When the custom tag appears in a JSP page, the JSP container generates code that creates a tag handler instance, calls the attribute setters, and invokes the doTag() method. Therefore, the tag handler methods are invoked during the execution of the JSP pages that use the custom tag.
Limitations and Workarounds
To simplify the tag handling API, JSP 2.0 imposes a restriction: page authors may not use JSP 1.x declarations (<%!...%>), JSP 1.x expressions (<%=...%>), and scriptlets (<%...%>) between <prefix:customTag> and </prefix:customTag> if the custom tag's handler is based on the Simple Tags API. In most cases, you can either move the Java code from the JSP page into the tag handler class or use JSTL with the JSP 2.0 expressions (${...}), which are allowed in the body of the custom tag. Note that JSP 2.0 lets you use scriptlets in the body of the custom tags that are based on the Classic Tags API. However, it's a good idea to avoid using Java code in your Web pages, because scriptless JSP pages are easier to maintain.
A previous Oracle Technology Network (OTN) article of mine"Using the JSP 2.0 EL API"explains another limitation of the Simple Tags API and provides a workaround. The JspContext class doesn't provide access to JSP implicit objects, such as application, session, request, and response. Most application servers, including Oracle Application Server Containers for J2EE (OC4J) 10g, let you cast the JSP context to PageContext
Tag handler classes are not suitable for generating large amounts of reusable HTML code with println() statements. JSP 2.0 has a much better solution for this task. The so-called tag files use the JSP syntax and are translated automatically by the JSP container into tag handler classes based on the Simple Tags API. Another OTN article of mine"Creating JSP 2.0 Tag Files"describes this new JSP feature.
Tags That Export Variables
Many JSTL tags implement some logic and export JSP variables to report the results. For example, <sql:query> has a var attribute that must specify the name of the JSP variable that will hold the SQL result set. The var attribute is optional for other JSTL tags such as <fmt:formatNumber> and <fmt:formatDate>. These tags output their result if the var attribute is not present. All JSTL tags that have a var attribute also have a scope attribute that can be used to indicate the scope of the JSP variable: page, request, session, or application.
The VarTagSupport class, which is an example developed for this article, extends SimpleTagSupport and provides setter methods for the var and scope attributes. Instead of implementing the doTag() method, VarTagSupport has utility methods for exporting a JSP variable, getting the body content, and outputting content. These methods are used by the subclasses of VarTagSupport within doTag(). This article contains four tag handler classes (EvalTag, MapTag, ListTag and ItemTag) that extend VarTagSupport.
Note that the JSP variables are called scoped variables in the JSTL specification and named variables or scoped attributes in the JSP specification. These variables are created/exported with the setAttribute() methods of the JspContext class. You can obtain their values with ${varName} in a JSP page and with the getAttribute() or findAttribute() methods of JspContext in your Java code. Do not confuse the JSP variables with the tag attributes.
Implementing Attribute Setters
The JSP container calls the attribute setters to communicate the values of the tag attributes to the custom tag handler. The setVar() method of VarTagSupport stores the value of the var attribute in a protected instance variable (varName). The setScope() method converts its parameter to an integer constant. If the parameter has a valid value (page, request, session, or application), the integer constant is stored in another protected instance variable (varScope). Otherwise, setScope() throws a JspException:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
public class VarTagSupport extends SimpleTagSupport {
protected String varName;
protected int varScope;
protected VarTagSupport() {
varScope = PageContext.PAGE_SCOPE;
}
public void setVar(String name) throws JspException {
varName = name;
}
public void setScope(String scope) throws JspException {
if (scope.equalsIgnoreCase("page"))
varScope = PageContext.PAGE_SCOPE;
else if (scope.equalsIgnoreCase("request"))
varScope = PageContext.REQUEST_SCOPE;
else if (scope.equalsIgnoreCase("session"))
varScope = PageContext.SESSION_SCOPE;
else if (scope.equalsIgnoreCase("application"))
varScope = PageContext.APPLICATION_SCOPE;
else
throw new JspException("Invalid scope: " + scope);
}
...
}
Exporting Variables to the JSP Environment
If the var attribute is present and has a non-null value (varName != null), the export() method obtains the JSP context with getJspContext(). Then, if the value parameter isn't null, export() sets a JSP variable with the setAttribute() method of the JSP context. The variable's value can be obtained in a JSP page with ${varName}. If the value parameter is null, export() calls removeAttribute(), which deletes any existing variable with the given name from the given scope.
If the var attribute isn't present or has a null value, the export() method returns false. Otherwise, export()returns true:
package jsputils.tags;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
public class VarTagSupport extends SimpleTagSupport {
...
protected boolean export(Object value) {
if (varName == null)
return false;
JspContext jspContext = getJspContext();
if (value != null)
jspContext.setAttribute(varName, value, varScope);
else
jspContext.removeAttribute(varName, varScope);
return true;
}
...
}
Getting the Content Generated by the Tag's Body
A tag handler class can obtain the JspFragment representing the body of the handled JSP tag with the getJspBody() method inherited from SimpleTagSupport. Then the tag handler can execute the JSP fragment with the invoke() method, which needs a java.io.Writer parameter if you want to capture the content generated by the JSP body. The invokeBody() method groups these operations, returning the body content as a String:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
import java.io.StringWriter;
import java.io.IOException;
public class VarTagSupport extends SimpleTagSupport {
...
protected String invokeBody() throws JspException {
JspFragment body = getJspBody();
StringWriter buffer = new StringWriter();
try {
body.invoke(buffer);
} catch (IOException x) {
throw new JspException(x);
}
return buffer.toString();
}
...
}
Note that you may call the invoke() method with a null parameter if you just want to output the content generated by the JSP body. If you don't call invoke(), the JSP body of the custom tag isn't executed.
Generating Content During the Tag's Execution
A tag handler class can output content by using the JspWriter returned by the getOut() method of the JSP context:
package jsputils.tags;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
import java.io.IOException;
public class VarTagSupport extends SimpleTagSupport {
...
protected void write(String str) throws JspException {
JspContext jspContext = getJspContext();
JspWriter out = jspContext.getOut();
try {
out.write(str);
} catch (IOException x) {
throw new JspException(x);
}
}
}
Developing a Custom Tag That Exports a Variable
"Using the JSP 2.0 EL API" discussed the development of a class named ELUtils, whose methods evaluate JSP expressions within Java code. The EL API is helpful when you want to use the EL outside of the JSP pages, such as in XML files. In "Using the JSP 2.0 EL API," we mapped the static methods of ELUtils to EL functions. This time, we will build a custom tag (<u:eval>) that calls one of the evaluate() methods of ELUtils. The <u:eval> tag is handled by a class named EvalTag, which implements two set methods (setExpr() and setType()) for the attributes of <u:eval>. The values of the expr and type attributes are passed to the evaluate() method, which returns the value of the expression.
If the expr attribute is not present, the doTag() method will call the invokeBody() method inherited from the VarTagSupport class to obtain the body content, which should be an expression. Therefore, the JSP page that invokes the <u:eval> tag may specify the expression as either the value of the expr attribute or between <u:eval> and </u:eval>.
The EvalTag class extends VarTagSupport, because it needs export() for creating a JSP variable with the name specified by the var attribute and the value returned by evaluate(). If the var attribute is not present, export() cannot set the variable and returns false. In this case, EvalTag outputs the value of the evaluated expression with the write() method inherited from VarTagSupport.
The source code of the EvalTag handler class follows:
package jsputils.tags;
import jsputils.el.ELUtils;
import javax.servlet.jsp.JspException;
public class EvalTag extends VarTagSupport {
private String strExpr;
private Object varType;
public EvalTag() {
varType = Object.class;
}
public void setExpr(String expr) throws JspException {
strExpr = expr;
}
public void setType(Object type) throws JspException {
varType = type;
}
protected Object evaluate(String expression,
Object expectedType) throws JspException {
return ELUtils.evaluate(
expression, expectedType, getJspContext());
}
public void doTag() throws JspException {
if (strExpr == null)
strExpr = invokeBody();
Object value = evaluate(strExpr, varType);
boolean exported = export(value);
if (!exported && value != null)
write(value.toString());
}
}
Defining the Custom Tag in a Library Descriptor
The <u:eval> tag is defined in an XML file named util.tld. The JSP container uses this file to map the custom tag to its handler class (EvalTag). In addition to the tag's name and the tag handler class, the descriptor contains information about the tag's body and attributes.
The body content is declared scriptless, which means that you may not use Java code (scriptlets) between <u:eval> and </u:eval>. If a tag doesn't use its body content, you should specify empty instead of scriptless. Note that for Classic Tags, you may also specify JSP, which allows you to use Java scriptlets in the tag's body. When developing handlers based on the Simple Tags API, you have to place all your Java code in the Java classes.
The util.tld descriptor defines four attributes for the <u:eval> tag: expr, type, var, and scope. All attributes are declared optional (required is false). The values of expr and type can contain JSP expressions (rtexprvalue is true), but the var and scope attributes must have fixed values (rtexprvalue is false).
The custom tag is described within a <taglib> element that contains a version number; a short name (prefix); and a Uniform Resource Identifier (URI), which doesn't have to indicate an existing Web resource:
<?xml version="1.0" encoding="UTF-8" ?>
<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 web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>u</short-name>
<uri>http://otn.oracle.com/jsp/taglib/util.tld</uri>
<tag>
<name>eval</name>
<tag-class>jsputils.tags.EvalTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>expr</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
...
</taglib>
Using the Custom Tag in a JSP Page
The web.xml descriptor of the example Web application defines two parameters: debug_mode and tags_db_dataSource. The debug_mode parameter indicates whether the application runs in a testing or a production environment. The tags_db_dataSource parameter uses the EL to select a datasource name, depending on the value of debug_mode:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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 web-app_2_4.xsd"
version="2.4">
<context-param>
<param-name>debug_mode</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>tags_db_dataSource</param-name>
<param-value>jdbc/${
initParam.debug_mode ? "dbtags" : "production"
}</param-value>
</context-param>
</web-app>
After importing the tag library of this article with <%@taglib%>, the EvalTest.jsp page uses the <u:eval> tag to evaluate the expression from the web.xml file:
<!-- EvalTest.jsp -->
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:eval expr="${initParam.tags_db_dataSource}" var="db"/>
${db}
<u:eval expr="${initParam.tags_db_dataSource}"/>
<u:eval>${initParam.tags_db_dataSource}</u:eval>
The JSP page tests the two ways of specifying the expression: with the expr attribute and between <u:eval> and </u:eval>. The var attribute is used to create a JSP variable named db, whose value is outputted with ${db}. When the var attribute is not present, the <u:eval> tag outputs the value of the evaluated expression. Here is the output generated by EvalTest.jsp:
jdbc/dbtags jdbc/dbtags jdbc/dbtags
Conditional Tags
JSTL provides several conditional tags (<c:if>, <c:choose>, <c:when>, and <c:otherwise>) and another tag for catching exceptions in a JSP page (<c:catch>). These tags are simple and very useful, but they don't benefit from the Fragment Attributes feature of JSP 2.0, which allows a single tag to handle multiple JSP fragments. This section of the article uses fragment attributes to build a more sophisticated conditional tag named <u:if> and handled by the IfTag class. The IfTag example also shows how to catch any exceptions that might occur during the execution of the JSP fragments.
Using Fragment Attributes
Suppose you have a form with two text fields (unitPrice and quantity) and you want to calculate the total price. You also want to handle situations in which users don't fill out the form or when they provide non-numeric values, which would generate a NumberFormatException. In a real application, you would probably use a framework such as JavaServer Faces (JSF) to generate the HTML form and to validate the user input. However, for the sake of testing the conditional tag developed in this section, assume you want to create the form without a specialized tag library. This is the code you would use:
<!-- IfTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<html>
<body>
<form method="post">
<c:set var="paramsProvided"
value="${!empty param.unitPrice and !empty param.quantity}"/>
...
<p> Unit Price:
<input type="text" name="unitPrice" size="10"
value="<c:out value='${param.unitPrice}'/>">
<p> Quantity:
<input type="text" name="quantity" size="10"
value="<c:out value='${param.quantity}'/>">
<p> <input type="submit" value="Calculate Price">
</form>
</body>
</html>
The following code shows how you can validate the form data with the <c:if> and <c:catch> tags of JSTL:
<c:if test="${paramsProvided}">
<c:catch var="error">
<c:set var="price"
value="${param.unitPrice * param.quantity}"/>
<p> Price: ${price}
</c:catch>
</c:if>
<c:if test="${not paramsProvided}">
<p> Please fill out the form
</c:if>
<c:if test="${error != null}">
<p> Number format error
</c:if>
The preceding code fragment is not part of IfTest.jsp. Instead of using JSTL, this page validates the form data with the <u:if> custom tag, which has a test attribute, like <c:if>. The <u:if> tag has three additional attributes named TRUE, FALSE, and ERROR. The values of these attributes are JSP fragments contained by the <jsp:attribute> standard action:
<u:if test="${paramsProvided}">
<jsp:attribute name="TRUE">
<c:set var="price"
value="${param.unitPrice * param.quantity}"/>
<p> Price: ${price}
</jsp:attribute>
<jsp:attribute name="FALSE">
<p> Please fill out the form
</jsp:attribute>
<jsp:attribute name="ERROR">
<p> Number format error
</jsp:attribute>
</u:if>
Declaring Fragment Attributes
You must declare the fragment attributes in a descriptor file with <fragment>true</fragment>:
<taglib ...>
...
<tag>
<name>if</name>
<tag-class>jsputils.tags.IfTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>TRUE</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<attribute>
<name>FALSE</name>
<required>false</required>
<fragment>true</fragment>
</attribute>
<attribute>
<name>ERROR</name>
<required>false</required>
<fragment>true</fragment>
</attribute>
</tag>
...
</taglib>
Handling Fragment Attributes
The IfTag handler class has set methods for the regular test attribute and for the TRUE, FALSE, and ERROR fragment attributes of <u:if>. The doTag() method executes the JSP fragments, using the invoke() method of the JspFragment class:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class IfTag extends SimpleTagSupport {
private boolean testAttr;
private JspFragment trueAttr;
private JspFragment falseAttr;
private JspFragment errorAttr;
public void setTest(boolean test) {
testAttr = test;
}
public void setTRUE(JspFragment fragment) {
trueAttr = fragment;
}
public void setFALSE(JspFragment fragment) {
falseAttr = fragment;
}
public void setERROR(JspFragment fragment) {
errorAttr = fragment;
}
public void doTag() throws JspException, IOException {
try {
if (testAttr) {
if (trueAttr != null)
trueAttr.invoke(null);
} else {
if (falseAttr != null)
falseAttr.invoke(null);
}
} catch (Exception x) {
if (errorAttr != null)
errorAttr.invoke(null);
else
throw new JspException(x);
}
}
}
Iteration Tags
JSTL has three iteration tags (<c:forEach>, <c:forTokens>, and <x:forEach>). This section shows how to implement a <u:while> tag, whose condition is reevaluated before each iteration by use of the EL API, covered in my previous article.
Using the Custom Tag with the JSTL Function Library
JSTL 1.1 provides a function library that implements many useful string operations. The following code tries to split a string (a1/a2//b//c1/c2/c3) into three tokens (a1/a2, b, and c1/c2/c3), using the fn:split() function of JSTL:
<!-- SplitTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<p>fn:split -
<c:set var="str" value="a1/a2//b//c1/c2/c3"/>
<c:set var="delim" value="//"/>
<c:set var="array" value="${fn:split(str, delim)}"/>
<c:forEach var="token" items="${array}">
[<c:out value="${token}"/>]
</c:forEach>
The preceding code doesn't produce the desired result, because fn:split() is based on java.util.StringTokenizer, which treats the delim parameter as a set of delimiter characters. Here is the output produced by SplitTest.jsp:
fn:split - [a1] [a2] [b] [c1] [c2] [c3]
JSTL offers other string functions, such as fn:contains(), fn:substringBefore(), and fn:substringAfter(), that can be used in a loop to split a1/a2//b//c1/c2/c3 into a1/a2, b, and c1/c2/c3. The loop is controlled by the <u:while> tag:
<!-- WhileTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<p>u:while -
<c:set var="str" value="a1/a2//b//c1/c2/c3"/>
<c:set var="delim" value="//"/>
<u:while test="\${fn:contains(str, delim)}">
[<c:out value="${fn:substringBefore(str, delim)}"/>]
<c:set var="str" value="${fn:substringAfter(str, delim)}"/>
</u:while>
[<c:out value="${str}"/>]
Note that the $ character is escaped with a backslash in the test condition. Therefore, the JSP container treats the value of the test attribute as text and doesn't evaluate the JSP expression. The <u:while> tag is handled by the WhileTag class, which evaluates the ${fn:contains(str, delim)} expression before each iteration. The ${fn:substringBefore(str, delim)} expression returns the first token of the string, and ${fn:substringAfter(str, delim)} returns the remaining tokens. The WhileTest.jsp page produces the desired output:
u:while - [a1/a2] [b] [c1/c2/c3]
Evaluating the Same JSP Expression Multiple Times
A tag handler class can invoke the body of a custom tag multiple times with the invoke() method. The fragment attributes can also be executed multiple times with the same invoke() method. If you want to reevaluate a regular attribute, however, you must use the EL API. The PEWrapper class from "Using the JSP 2.0 EL API" allows you to optimize this process by parsing the JSP expression once. Note that this example class is just a wrapper around the standard EL API. The doTag() method of the WhileTag handler class creates a PEWrapper instance, which needs the expression that must be evaluated (strTest), the expected type (Boolean), the JSP context, and a function mapper that allows you to use JSTL functions within the JSP expression. The FNMapper class (presented in "Using the JSP 2.0 EL API") maps the JSTL functions to a set of static Java methods provided by the Apache implementation of JSTL. The doTag() method executes the JSP body of the <u:while> tag while the test expression is true:
package jsputils.tags;
import jsputils.el.PEWrapper;
import jsputils.el.FNMapper;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class WhileTag extends SimpleTagSupport {
private String strTest;
public void setTest(String test) throws JspException {
strTest = test;
}
public void doTag() throws JspException, IOException {
PEWrapper parsedExpr = PEWrapper.getInstance(
strTest, Boolean.class, getJspContext(),
FNMapper.getInstance("fn"));
while (((Boolean) parsedExpr.evaluate()).booleanValue())
getJspBody().invoke(null);
}
}
The mapping between <u:while> and the WhileTag class is defined in the util.tld file:
<taglib ...>
...
<tag>
<name>while</name>
<tag-class>jsputils.tags.WhileTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
...
</taglib>
Tags with Dynamic Attributes
JSTL offers support for Java collections. For example, you can use <c:set> to add an element to a java.util.Map. Map objects, however, cannot be created with JSTL. This section builds a custom tag that creates a JSP variable holding a java.util.LinkedHashMap. You can create maps in JSP pages, as in the following example:
<u:map var="langMap" scope="application"
en="English" de="Deutsch" fr="Fraais"
it="Italiano" es="Espol"/>
The map is initialized with the dynamic attributes of the tag (en, de, fr, it, and es). These attributes are called dynamic because they aren't declared in the library descriptor.
Handling Dynamic Attributes
A custom tag can handle dynamic attributes, by implementing the DynamicAttributes interface, whose single method is setDynamicAttribute(). The JSP container calls this method for each attribute that isn't declared in the TLD file.
The MapTag handler class extends VarTagSupport and implements DynamicAttributes. The LinkedHashMap instance is created in the MapTag() constructor. The setDynamicAttribute() method adds an element to the map, using the attribute's name as a key for the attribute's value. The doTag() method exports the map object as a JSP variable, using the export() method inherited from VarTagSupport:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.DynamicAttributes;
import java.util.LinkedHashMap;
public class MapTag extends VarTagSupport
implements DynamicAttributes {
private LinkedHashMap map;
public MapTag() {
map = new LinkedHashMap();
}
public void setDynamicAttribute(String uri,
String localName, Object value)
throws JspException {
map.put(localName, value);
}
public void doTag() throws JspException {
export(map);
}
}
Note that LinkedHashMap preserves the order of its elements, meaning that the java.util.Iterator returned by the iterator() method provides access to the map's elements in the order in which they were added to the map.
The support for dynamic attributes must be declared in the util.tld file, along with the var and scope attributes of the <u:map> tag:
<taglib ...>
...
<tag>
<name>map</name>
<tag-class>jsputils.tags.MapTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
...
</taglib>
Using Map Objects in JSP Pages
The MapTest.jsp page uses the <u:map> tag to create two maps (langMap and versionMap) containing the supported languages and versions of a hypothetical Web site. Then the page redirects the user to MapTest2.jsp, using the <c:redirect> tag of JSTL:
<!-- MapTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:map var="langMap" scope="application"
en="English" de="Deutsch" fr="Fraais"
it="Italiano" es="Espol"/>
<c:set var="defaultLang" scope="application" value="en"/>
<u:map var="versionMap" scope="application"
html="HTML" java="Java" flash="Flash"/>
<c:set var="defaultVersion" scope="application" value="html"/>
<c:redirect url="MapTest2.jsp"/>
The MapTest2.jsp page creates another map (prefMap) if this JSP variable doesn't already exist in the session scope. The three maps, containing languages, versions, and user preferences, are used to create a simple HTML form that lets users change their preferred language and site version:
<!-- MapTest2.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<c:if test="${prefMap == null}">
<u:map var="prefMap" scope="session"
lang="${defaultLang}" version="${defaultVersion}"/>
</c:if>
<form method="post" action="MapTest3.jsp">
<p>Language: <br>
<select name="lang" size="1">
<c:forEach var="lang" items="${langMap}">
<c:set var="selected" value=""/>
<c:if test="${lang.key == prefMap.lang}">
<c:set var="selected" value="selected"/>
</c:if>
<option value="${lang.key}" ${selected}>
${lang.value}
</option>
</c:forEach>
</select>
<p>Version: <br>
<c:forEach var="version" items="${versionMap}">
<c:set var="checked" value=""/>
<c:if test="${version.key == prefMap.version}">
<c:set var="checked" value="checked"/>
</c:if>
<input type="radio" name="version" ${checked}
value="${version.key}"> ${version.value} <br>
</c:forEach>
<p> <input type="submit" value="Save">
</form>
Here is the HTML form generated by MapTest2.jsp:
<form method="post" action="MapTest3.jsp">
<p>Language: <br>
<select name="lang" size="1">
<option value="en" selected>
English
</option>
<option value="de" >
Deutsch
</option>
<option value="fr" >
Fraais
</option>
<option value="it" >
Italiano
</option>
<option value="es" >
Espol
</option>
</select>
<p>Version: <br>
<input type="radio" name="version" checked
value="html"> HTML <br>
<input type="radio" name="version"
value="java"> Java <br>
<input type="radio" name="version"
value="flash"> Flash <br>
<p> <input type="submit" value="Save">
</form>
The MapTest3.jsp saves the preferences selected by the user into the prefMap variable and shows the user preferences:
<!-- MapTest3.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:map var="prefMap" scope="session"
lang="${param.lang}" version="${param.version}"/>
<p>User Preferences:
<ul>
<li>Language: ${prefMap.lang}</li>
<li>Version: ${prefMap.version}</li>
</ul>
<form method="post" action="MapTest2.jsp">
<p> <input type="submit" value="Change">
</form>
Cooperating Tags
The <sql:param> tag of JSTL cooperates with <sql:query> and <sql:update>, communicating the parameters of the SQL statements that are executed. The same technique is used in this article to implement two custom tags (<u:list> and <u:item>) that cooperate to create a java.util.ArrayList in a JSP page. The <u:list> tag can be the parent of one or more <u:item> tags that contain the list's elements, as in the following example:
<u:list var="services">
<u:item> E-Mail </u:item>
<u:item> Web Hosting </u:item>
<u:item> E-Commerce </u:item>
</u:list>
The <u:item> tag doesn't need to be contained directly by <u:list>. You can place other tags, such as <c:forEach>, between <u:list> and <u:item>. This allows you to add the elements in a loop. The values of these elements can be specified between <u:item> and </u:item>, as in the preceding code fragment, or they can be specified with the value attribute of <u:item>:
<u:list var="selectedServices">
<c:forEach var="index" items="${paramValues.selected}">
<c:set var="paramName" value="service${index}"/>
<u:item value="${param[paramName]}"/>
</c:forEach>
</u:list>
These code examples are part of the ListTest.jsp and ListTest2.jsp pages, which are presented after the ListTag and ItemTag classes.
Implementing Tag Handlers That Cooperate
The ListTag handler class creates the ArrayList instance in its constructor and provides a public add() method that adds an element to the list. The doTag() method invokes the body of the handled <u:list> tag and exports the list as a JSP variable:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import java.util.ArrayList;
import java.io.IOException;
public class ListTag extends VarTagSupport {
private ArrayList list;
public ListTag() {
list = new ArrayList();
}
public void add(Object item) {
list.add(item);
}
public void doTag() throws JspException, IOException {
getJspBody().invoke(null);
export(list);
}
}
The <u:item> tag is handled by the ItemTag class, whose doTag() method uses findAncestorWithClass() to locate the ListTag instance that handles the nearest <u:list> ancestor tag. The list element is passed to the add() method of the ancestor's handler:
package jsputils.tags;
import javax.servlet.jsp.JspException;
public class ItemTag extends VarTagSupport {
private Object itemValue;
public void setValue(Object value) throws JspException {
itemValue = value;
}
public void doTag() throws JspException {
ListTag ancestor = (ListTag) findAncestorWithClass(
this, ListTag.class);
if (ancestor == null)
throw new JspException(
"Couldn't find 'list' ancestor for 'item'");
if (itemValue == null)
itemValue = invokeBody();
ancestor.add(itemValue);
}
}
The two tags are declared in the TLD file like any other regular tags:
<taglib ...>
...
<tag>
<name>list</name>
<tag-class>jsputils.tags.ListTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<name>item</name>
<tag-class>jsputils.tags.ItemTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
Using List Objects in JSP Pages
The ListTest.jsp page creates a list containing services and uses it to generate an HTML form that allows users to select one or more services:
|
Resources
Use the following resources to test the examples and learn more about the JSP 2.0 Simple Tags API.
Download the source code. The jsptags_src.zip file contains the examples for this article: The jsputils directory groups the Java classes, and jsptags is a Java Web application. To run the examples, you need J2SE, a J2EE 1.4 application server, and JSTL 1.1.
Read "Creating JSP 2.0 Tag Files." Andrei Cioroianu shows how to create and use tag files and how to transform existing page fragments into tag files. He uses JSTL and several advanced JSP features to build tag files that update and query a database.
Read "Using the JSP 2.0 EL API." Andrei Cioroianu shows how to evaluate JSP expressions dynamically, use the Expression Language in XML configuration files, and optimize the EL usage when presenting SQL result sets.
Download OC4J 10g. OC4J 10g Developer Preview 2 fully implement the J2EE 1.4 specs, which include JSP 2.0. You can use OC4J 10g (10.0.3) to test the examples.
Download JSTL 1.1. Before deploying the jsptags Web application, download JSTL and copy the jstl.jar and standard.jar files into the jsptags/WEB-INF/lib directory.
Read the JSP 2.0 specification. The JSP 2.0 specification has an entire chapter ("Part I: Chapter JSP.7 Tag Extensions") for developers who want to build custom tag libraries. The Simple Tags API and Classic Tags API are described in another chapter ("Part II: Chapter JSP.13 Tag Extension API")
JSP samples and tutorials. JSP sample code
Tutorial: "Understanding the New Features of JSP 2.0"
|
<!-- ListTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:list var="services">
<u:item> E-Mail </u:item>
<u:item> Web Hosting </u:item>
<u:item> E-Commerce </u:item>
</u:list>
<form method="post" action="ListTest2.jsp">
<p> Services: <br>
<c:forEach var="index" begin="${0}"
end="${fn:length(services)-1}">
<input type="checkbox" checked
name="selected" value="${index}">
<c:out value="${services[index]}"/>
<input type="hidden" name="service${index}"
value="${services[index]}"> <br>
</c:forEach>
<p> <input type="submit" value="Select">
</form>
Here is the HTML form generated by ListTest.jsp:
<form method="post" action="ListTest2.jsp">
<p> Services: <br>
<input type="checkbox" checked
name="selected" value="0">
E-Mail
<input type="hidden" name="service0"
value=" E-Mail "> <br>
<input type="checkbox" checked
name="selected" value="1">
Web Hosting
<input type="hidden" name="service1"
value=" Web Hosting "> <br>
<input type="checkbox" checked
name="selected" value="2">
E-Commerce
<input type="hidden" name="service2"
value=" E-Commerce "> <br>
<p> <input type="submit" value="Select">
</form>
The ListTest2.jsp page handles user-submitted form data, creating and showing the list of selected services:
<!-- ListTest2.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:list var="selectedServices">
<c:forEach var="index" items="${paramValues.selected}">
<c:set var="paramName" value="service${index}"/>
<u:item value="${param[paramName]}"/>
</c:forEach>
</u:list>
<p> Selected Services: <br>
<ul>
<c:forEach var="service" items="${selectedServices}">
<li><c:out value="${service}"/></li>
</c:forEach>
</ul>
Summary
No matter how many tags are provided by JSTL and other tag libraries, there are situations in which you have to develop your own tags. This article showed how to use the Simple Tags API and the basic techniques needed when developing custom tags. In addition, it included several tags for you to reuse in your Web applications.
Andrei Cioroianu (devtools@devsphere.com) is the founder of Devsphere (www.devsphere.com), a provider of Java frameworks, XML consulting, and Web development services. Cioroianu has written many Java articles, published by ONJava (www.onjava.com), JavaWorld (www.javaworld.com), and Java Developer's Journal. He also coauthored the books Java XML Programmer's Reference and Professional Java XML (both from Wrox Press).
|