Custom Tags
Two mechanisms now supported:
- classic - requires Java code, tag library descriptor
- tag files - tags are implemented in a JSP-like file, no descriptor needed
Typical tag scenarios:
- simple tag - no body or attributes
- simple tag with attributes - no body
- tag with body
- iterative tag
- tags with shared context
JSP 1.0 Tags
These tags are implemented via Java classes, known as tag handlers, and require a descriptor file.
This is the classic approach, starting from JSP 1.0.
Tag handlers implement the Tag or BodyTag interfaces, and can be derived from TagSupport and BodyTagSupport.
All of these are in the javax.servlet.jsp.tagext package.
Typical methods to be implemented:
- doStartTag - called when the start tag is encountered
- doEndTag - called when the end tag is encountered
- doInitBody - called before the body is evaluated
- doAfterBody - called after the body is evaluated
- release - ???
- set/get attribute - called to pass in attribute values
The descriptor file
The descriptor is a .tld file, in XML format.
It starts with the xml declaration and doctype:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
...
</taglib>
The following elements are permitted as children of <taglib>:
| tlib-version | The version of the library |
| jsp-version | The JSP version targeted |
| short-name | optinal name |
| uri | A URI that uniquely identifies the library |
| display-name | optional name |
| small-icon | optional icon |
| large-icon | optional icon |
| description | optional description |
| listener | Defines a listener; includes a nested listener-class element |
| tag | Defines a tag (see below) |
The <tag> element has the following child elements:
| name | The tag name |
| tag-class | Fully qualified name of the implementation class |
| tei-class | Fully qualified name of the TagExtraInfo class, used to define scripting variables that are exported to the calling JSP. This is not needed if there are no exported scripting variables. Furthermore, the <variable> element can be used instead. |
| body-content | The body content type: 'empty', 'JSP' or 'tagdependent' |
| display-name | Optional display name |
| small-icon | Optional small icon |
| large-icon | Optional large icon |
| description | Optional description |
| variable | Defines an exported scripting variable (see below) |
| attribute | Defines an attribute (see below) |
The variable element defines an exported scripting variable
and has the following attributes:
| name-given | The name of the variable |
| name-from-attribute | The name of the attribute whose value will define the name of the variable. Exactly one of name-given and name-from-attribute must be supplied. |
| variable-class | The class of the variable. The default is java.lang.String. |
| declare | Whether the variable refers to a new object. The default is true. |
| scope | The scope of the variable: NESTED, AT_BEGIN, AT_END. |
Finally, the <attribute> element has the following child elements:
| name | The name of the attribute |
| required | Whether the attribute is required or not: 'true', 'false', 'yes' or 'no' |
| rtexprvalue | Whether a runtime expression is accepted: 'true', 'false', 'yes' or 'no' |
| type | The fully-qualified type of the variable |
Simple Tags
Simple tags have no attributes, do not evaluate their bodies, and export no scripting variables.
They are used like this:
<asitl:now/>
The JSP file, test-simple.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Tag Test</title>
</head>
<body>
It is now <asitl:now/>
</body>
</html>
The output:
It is now Mon May 02 10:12:02 GMT-05:00 2005
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
<tag>
<name>now</name>
<tag-class>net.alethis.tags.CurrentDateTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
The Java class, CurrentDateTag.java:
package net.alethis.tags;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class CurrentDateTag extends TagSupport {
public int doStartTag() throws JspException {
try {
Date d = new Date();
pageContext.getOut().print(d.toString());
}
catch (Exception e) {
throw new JspTagException("error in CurrentDateTag: " + e.getMessage());
}
return SKIP_BODY;
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The jar file structure:
test-simple.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
CurrentDateTag.class
Tags with Attributes
The attributes are received in the tag class via setter methods.
The JSP file, test-attributes.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %>
<html3
<head>
<title>JSP Tag Test</title>
</head>
<body>
<c:set var="x" value="38"/>
The square of ${x} is <asitl:power exponent="2" value="${x}"/>
<br>
The cube of ${x} is <asitl:power exponent="3" value="${x}"/>
</body>
</html>
We're using the JSTL to set up the variable x.
And the output:
The square of 38 is 1444
The cube of 38 is 54872
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>power</name>
<tag-class>net.alethis.tags.PowerTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>exponent</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
The implementation class, PowerTag.java:
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class PowerTag extends TagSupport {
private String exponentString;
private String valueString;
public void setExponent(String s) {
exponentString = s;
}
public void setValue(String s) {
valueString = s;
}
public int doStartTag() throws JspException {
try {
int exp = Integer.parseInt(exponentString);
int val = Integer.parseInt(valueString);
int result = 1;
for (int i = 0; i < exp; i++) {
result = result * val;
}
pageContext.getOut().print(result);
}
catch (Exception e) {
throw new JspTagException("error in PowerTag: " + e.getMessage());
}
return SKIP_BODY;
}
public int doEndTag() {
return EVAL_PAGE;
}
}
Note that the tag only works for positive exponents!
The jar file structure (including the necessary JSTL files):
test-attributes.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
CurrentDateTag.class
lib
jstl.jar
standard.jar
Tags with Bodies
The techniques to be used depend on what is to be done with the body.
The first distinction is whether the tag 'interacts' with the body.
Interaction is required if the tag needs to read or modify the body content.
A second distinction is whether the tag implements some kind of iteration,
where the body content is evaluated multiple times.
The body-content element in the descriptor file should be set to 'tagdependent'
if the body is plain template text, or 'JSP' if it includes expressions,
jsp tags or custom tags.
The general possibilities are:
| No interaction, no iteration | Implement Tag, or inherit from TagSupport; the doStartTag() method should return EVAL_BODY_INCLUDE |
| No interaction, with iteration | Implement IterationTag, or inherit from TagSupport; doStartTag() should return EVAL_BODY_INCLUDE (or SKIP_BODY) and doAfterBody() should return EVAL_BODY_AGAIN as long as further iterations are required, and SKIP_BODY at the end. |
| Interaction, no iteration | Implement BodyTag, or inherit from BodyTagSupport; implement doInitBody() and doAfterBody(); the doStartTag() method should return EVAL_BODY_BUFFERED |
| Interaction and iteration | Implement BodyTag, or inherit from BodyTagSupport; implement doInitBody() and doAfterBody(); thedoStartTag() and doAfterBody() should return EVAL_BODY_AGAIN as long as further iterations are required, and SKIP_BODY at the end. |
To summarize, doStartTag() should return:
- SKIP_BODY - if the body does not need to be evaluated
- EVAL_BODY_INCLUDE - if the body is to be included, but not read or modified by the tag (the container will not make the BodyContent object available)
- EVAL_BODY_BUFFERED - if the body is to be read or modified; in this case the container will call setBodyContent() on the tag to initialize the bodyContent; this method is available only in the BodyTag interface, so it is an error to return this value from a tag that only implements Tag
Including the Body
This example conditionally includes the body depending on the value of an attribute.
The JSP file, test-include-body.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %>
<html3
<head>
<title>JSP Tag Test</title>
</head>
<body>
<c:set var="x" value="38"/>
<asitl:ifpositive value="${x}">
First number is positive
</asitl:ifpositive>
<c:set var="x" value="-38"/>
<asitl:ifpositive value="${x}">
Second number is positive
</asitl:ifpositive>
</body>
</html>
The output:
First number is positive
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>ifpositive</name>
<tag-class>net.alethis.tags.IfPositiveTag</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
The implementation class, PowerTag.java:
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class IfPositiveTag extends TagSupport {
private String valueString;
public void setValue(String s) {
valueString = s;
}
public int doStartTag() throws JspException {
try {
int val = Integer.parseInt(valueString);
if (val < 0) {
return SKIP_BODY;
}
else {
return EVAL_BODY_INCLUDE;
}
}
catch (Exception e) {
throw new JspTagException("error in IfPositiveTag: " + e.getMessage());
}
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The jar file structure (including the necessary JSTL files):
test-include-body.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
IfPositiveTag.class
lib
jstl.jar
standard.jar
Repeating the Body
This example repeats a body a specified number of times.
The JSP file, test-iterate.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html3
<head>
<title>JSP Tag Test</title>
</head>
<body>
And the answer is<asitl:repeat count="17">.</asitl:repeat> 42!
</body>
</html>
The output:
And the answer is................. 42!
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>repeat</name>
<tag-class>net.alethis.tags.RepeatTag</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>count</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
The implementation class, RepeatTag.java:
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class RepeatTag extends TagSupport {
private String countString;
private int count;
public void setCount(String s) {
countString = s;
}
public int doStartTag() throws JspException {
try {
count = Integer.parseInt(countString);
if (count > 0) {
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
catch (Exception e) {
throw new JspTagException("error in RepeatTag: " + e.getMessage());
}
}
public int doAfterBody() {
count--;
if (count > 0) {
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The jar file structure (including the necessary JSTL files):
test-iterate.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
RepeatTag.class
Reading the Body
This example shows a tag that reads its body, and replaces the body text
with generated text.
Tags of this nature must implement BodyTag, or inherit from BodyTagSupport.
The content of the body is accessible in doAfterBody via the getBodyContent
method, which returns a BodyContent object.
The getString() method on this object gets the content.
To write to the output stream, use the JspWriter obtained by calling
getEnclosingWriter on the BodyContent object.
This ensures that the content will be available to enclosing tags.
The JSP file, test-read-body.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Tag Test</title>
</head>
<body>
3 * 7 = <asitl:evaluate>3 * 7</asitl:evaluate>
<br>
14 + 8 = <asitl:evaluate>14 + 8</asitl:evaluate>
<br>
723 - 194 = <asitl:evaluate>723 - 194</asitl:evaluate>
<br>
37 / 4 = <asitl:evaluate>37 / 4</asitl:evaluate>
</body>
</html>
The output:
3 * 7 = 21
14 + 8 = 22
723 - 194 = 529
37 / 4 = 9
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>evaluate</name>
<tag-class>net.alethis.tags.EvaluateTag</tag-class>
<body-content>tagdependent</body-content>
</tag>
</taglib>
The implementation class, EvaluateTag.java.
The doStartTag() method must return EVAL_BODY_BUFFERED in order that the
BodyContent object be made available.
package net.alethis.tags;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class EvaluateTag extends BodyTagSupport {
public int doStartTag() throws JspException {
return EVAL_BODY_BUFFERED;
}
public int doAfterBody() throws JspException {
try {
BodyContent bc = getBodyContent();
String expression = bc.getString();
bc.clearBody();
StringTokenizer st = new StringTokenizer(expression);
int first = Integer.parseInt(st.nextToken());
String op = st.nextToken();
int second = Integer.parseInt(st.nextToken());
int result = 0;
if (op.equals("+")) {
result = first + second;
}
else if (op.equals("-")) {
result = first - second;
}
else if (op.equals("*")) {
result = first * second;
}
else if (op.equals("/")) {
result = first / second;
}
JspWriter out = bc.getEnclosingWriter();
out.print(result);
bodyContent.clearBody();
return SKIP_BODY;
}
catch (Exception e) {
throw new JspTagException("error in EvaluateTag: " + e.getMessage());
}
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The jar file structure:
test-read-body.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
EvaluateTag.class
Scripting Variables in the Body
This example shows a tag that evaluates its body iteratively,
and provides scripting variables used in the body.
Since we're not actually reading or modifying the body, we can
inherit from TagSupport.
The doStartTag() method must return EVAL_BODY_INCLUDE rather than
EVAL_BODY_BUFFERED to indicate the BodyContent object is not needed;
otherwise, the container attempts to call the method that sets the
BodyContent, which is only defined in the BodyTag interface.
Also, since the scripting variables are used only in the body
of the custom tag, the NESTED scope is appropriate.
The JSP file, test-variable.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Tag Test</title>
</head>
<body>
<asitl:powertable from="1" to="10">
<tr>
<td>${value}</td>
<td>${squared}</td>
<td>${cubed}</td>
</tr>
</asitl:powertable>
</body>
</html>
The output:
| Value | Square | Cube |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 4 | 8 |
| 3 | 9 | 27 |
| 4 | 16 | 64 |
| 5 | 25 | 125 |
| 6 | 36 | 216 |
| 7 | 49 | 343 |
| 8 | 64 | 512 |
| 9 | 81 | 729 |
| 10 | 100 | 1000 |
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>powertable</name>
<tag-class>net.alethis.tags.PowerTableTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>from</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>to</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<variable>
<name-given>value</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
<variable>
<name-given>squared</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
<variable>
<name-given>cubed</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
</tag>
</taglib>
The implementation class, PowerTableTag.java:
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class PowerTableTag extends TagSupport {
private String fromString;
private String toString;
private int from;
private int to;
private int curr;
public void setFrom(String s) {
fromString = s;
}
public void setTo(String s) {
toString = s;
}
public int doStartTag() throws JspException {
try {
pageContext.getOut().println("<table border='1'>");
pageContext.getOut().println("<tr>");
pageContext.getOut().println("<th>Value</th>");
pageContext.getOut().println("<th>Square</th>");
pageContext.getOut().println("<th>Cube</th>");
pageContext.getOut().println("</tr>");
from = Integer.parseInt(fromString);
to = Integer.parseInt(toString);
curr = from;
pageContext.setAttribute("value", String.valueOf(curr));
pageContext.setAttribute("squared", String.valueOf(curr * curr));
pageContext.setAttribute("cubed", String.valueOf(curr * curr * curr));
return EVAL_BODY_INCLUDE;
}
catch (Exception e) {
throw new JspTagException("error in RepeatTag: " + e.getMessage());
}
}
public int doAfterBody() {
curr++;
if (curr <= to) {
pageContext.setAttribute("value", String.valueOf(curr));
pageContext.setAttribute("squared", String.valueOf(curr * curr));
pageContext.setAttribute("cubed", String.valueOf(curr * curr * curr));
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
public int doEndTag() throws JspException {
try {
pageContext.getOut().println("</table>");
return EVAL_PAGE;
}
catch (Exception e) {
throw new JspTagException("error in RepeatTag: " + e.getMessage());
}
}
}
The jar file structure:
test-variable.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
PowerTableTag.class
Scripting Variables in the Rest of the Page
This example shows a tag that provides a scripting variable for
the rest of the page.
The appropriate scope is AT_END.
The JSP file, test-variable2.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Tag Test</title>
</head>
<body>
<asitl:provideBrowser/>
Your browser is ${browser}.
</body>
</html>
The output:
Your browser is Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8b) Gecko/20050201 Firefox/1.0+.
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
<tag>
<name>provideBrowser</name>
<tag-class>net.alethis.tags.BrowserTag</tag-class>
<body-content>empty</body-content>
<variable>
<name-given>browser</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>AT_END</scope>
</variable>
</tag>
</taglib>
The implementation class, BrowserTag.java:
package net.alethis.tags;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class BrowserTag extends TagSupport {
public int doStartTag() throws JspException {
String browser = ((HttpServletRequest) pageContext.getRequest()).getHeader("User-Agent");
pageContext.setAttribute("browser", browser);
return SKIP_BODY;
}
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
}
The jar file structure:
test-variable2.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
BrowserTag.class
Dynamic Attributes
The ability of a tag to handle dynamic attributes was introduced in JSP 2.0.
The tag handler must implement DynamicAttributes, which provides the
method setDynamicAttribute(), and the tld descriptor file must indicate
that the tag accepts dynamic attributes (and must use the JSP 2.0 DTD!).
The JSP file, test-dynamic.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Tag Test</title>
</head>
<body>
<asitl:mapTable a="1" b="5" c="14" d="42"/>
<br>
<asitl:mapTable vvv="niece" www="daughter" xxx="sister" yyy="mother" zzz="aunt"/>
<br>
<asitl:mapTable rr="green" gg="blue" bb="red"/>
</body>
</html>
The output:
| a | 1 |
| b | 5 |
| c | 14 |
| d | 42 |
| vvv | niece |
| www | daughter |
| xxx | sister |
| yyy | mother |
| zzz | aunt |
| rr | green |
| gg | blue |
| bb | red |
The descriptor file, asitl.tld.
Note that it uses the JSP 2.0 format.
In particular, for validation, an XML Schema is used instead of a DTD.
<?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/web-jsptaglibrary_2_0.xsd'
version='2.0'>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>mapTable</name>
<tag-class>net.alethis.tags.MapTableTag</tag-class>
<body-content>empty</body-content>
<dynamic-attributes>true</dynamic-attributes>
</tag>
</taglib>
The implementation class, MapTableTag.java:
package net.alethis.tags;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MapTableTag extends TagSupport implements DynamicAttributes {
private List names = new ArrayList();
private List values = new ArrayList();
public void setDynamicAttribute(String ns, String name, Object value) {
names.add(name);
values.add(value);
}
public int doStartTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
out.println("<table border='1'>");
for (int i = 0; i < names.size(); i++) {
out.print("<tr>");
out.print("<td>");
out.print((String) names.get(i));
out.println("</td>");
out.print("<td>");
out.print(values.get(i).toString());
out.println("</td>");
out.print("</tr>");
}
out.println("</table>");
return SKIP_BODY;
}
catch (Exception e) {
throw new JspTagException("error in MapTableTag: " + e.getMessage());
}
}
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
}
The jar file structure:
test-dynamic.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
MapTableTag.class
Cooperating Tags
Custom tags in a hierarchy can cooperate with each other.
The basic mechanism is for a child tag implementation to access the
implementation of a parent tag, or indeed, of any ancestor tag.
TagSupport includes getParent() and setParent() methods that are used
to traverse the hierarchy, and it also includes findAncestorWithClass(),
to search for an arbitary ancestor by class.
This is the method used in the following example.
The JSP file, test-wall.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Test Tag</title>
</head>
<body>
<asitl:wall start='10'>
<asitl:bottle/>
<br>
</asitl:wall>
</body>
</html>
The output:
10 bottles of beer on the wall
9 bottles of beer on the wall
8 bottles of beer on the wall
7 bottles of beer on the wall
6 bottles of beer on the wall
5 bottles of beer on the wall
4 bottles of beer on the wall
3 bottles of beer on the wall
2 bottles of beer on the wall
1 bottles of beer on the wall
0 bottles of beer on the wall
The descriptor file, asitl.tld.
<?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/web-jsptaglibrary_2_0.xsd'
version='2.0'>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>wall</name>
<tag-class>net.alethis.tags.WallTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>start</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>bottle</name>
<tag-class>net.alethis.tags.BottleTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
The first implementation class, WallTag.java.
It iterates the body from the start count down to zero:
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class WallTag extends TagSupport {
private String startString;
private int curr;
public void setStart(String s) {
startString = s;
}
public int getCurrentCount() {
return curr;
}
public int doStartTag() throws JspException {
try {
curr = Integer.parseInt(startString);
if (curr > 0) {
return EVAL_BODY_INCLUDE;
}
else {
return SKIP_BODY;
}
}
catch (Exception e) {
throw new JspTagException("error in WallTag: " + e.getMessage());
}
}
public int doAfterBody() {
curr--;
if (curr >= 0) {
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The second implementation class, BottleTag.java.
It accesses the wall tag in order to determine the current count
of the number of bottles left on the wall.
package net.alethis.tags;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class BottleTag extends TagSupport {
public int doStartTag() throws JspException {
try {
WallTag wall = (WallTag) findAncestorWithClass(this, WallTag.class);
int curr = wall.getCurrentCount();
pageContext.getOut().println(curr + " bottles of beer on the wall");
return SKIP_BODY;
}
catch (Exception e) {
throw new JspTagException("error in BottleTag: " + e.getMessage());
}
}
public int doEndTag() {
return EVAL_PAGE;
}
}
The jar file structure:
test-wall.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
BottleTag.class
WallTag.class
JSP 2.0 Tag Files
With JSP 2.0, two new mechanisms were introduced to simplify the creation of
custom tags.
The first of these is known as 'tag files', which implement custom tags by means
of files which look very much like JSP pages.
They have the .tag or .tagx extension (classic style vs. XML style), and included
fragments have a .tagf extension.
This mechanism is particularly useful for tags which include a lot of template text.
The files should be placed under WEB-INF/tags.
Implicit objects available:
- request
- response
- session
- application
- out
- config
- jspContext
jspContext replaces pageContext to reduce dependency on Java.
Example: simple tag with no body or attributes:
<%@ tag import="java.util.*" import="java.text.*" %>
<%
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
Date d = new Date(System.currentTimeMillis());
out.println(df.format(d));
%>
Store this as currDate.tag in WEB-INF/tags, and the <currDate> tag becomes available.
Here's a typical JSP file:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %>
<html>
<head>
</head>
<body>
Today is <util:currDate/>.
</body>
</html>
Directives
Directives available in tag files:
| tag | similar to the page directive for JSP pages |
| include | includes other tag files |
| taglib | use another custom tag library |
| attribute | declare an attribute |
| variable | define a scripting variable that becomes visible in the JSP page |
The tag directive
The tag directive has the following attributes:
| body-content | describes the treatment of the body: 'empty', 'tagdependent' or 'scriptless' |
| import | imports classes or packages; this attribute can appear multiple times |
| pageEncoding | as in the page directive |
| isELIgnored | as in the page directive |
| dynamic-attributes | specifies the name of a map that will receive all undeclared attributes |
| language | the scripting language, must be 'java' currently |
| display-name | for tools - optional - default is the tag file name, minus extension |
| small-icon | for tools |
| large-icon | for tools |
| description | a description of the tag |
| example | informal description of how the tag is used |
There may be multiple tag directives, but only the import attribute can appear
multiple times among the union of all of their attributes.
The include directive
The include directive includes other files. It has a single 'file' attribute.
<%@ include file='commonheader.tagf' %>
The taglib directive
The taglib directive is as for JSP pages.
The attribute directive
The attribute directive declares an attribute that the tag accepts.
The attributes of the attribute directive are:
| name | the name of the attribute |
| required | true or false |
| rtexprvalue | true or false - indicates whether runtime expressions are accepted; true is the default |
| type | the value type - default is java.lang.String |
| fragment | true or false - should the value be evaluated by the container (false), or passed directly to the tag handler (true) |
| description | a description for the attribute |
This tag creates a number of copies of a string; save it as
WEB-INF/tags/repeater.tag:
<%@ attribute name="count" type="java.lang.Integer" required="true" %>
<%@ attribute name="value" type="java.lang.String" required="true" %>
<%!
private String repeater(Integer count, String s) {
int n = count.intValue();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; i++) {
sb.append(s);
}
return sb.toString();
}
%>
<%
out.println(repeater(count, value));
%>
This tag will accept run-time expressions for both attributes, since true
is the default for rtexprvalue.
Use the tag like this:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %>
<html>
<head>
</head>
<body>
Let's get some sleep! <util:repeater count='${3 * 10}' value='zzz'/>
</body>
</html>
The variable directive
The variable directive exports a variable to the JSP page.
The attributes are:
| name-given | The variable name that will be available in the calling JSP page. |
| name-from-attribute | Specifies the name of an attribute, whose value is the name of the variable that will be available in the calling JSP page. Exactly one of name-given or name-from-attribute must be supplied. |
| alias | A locally scoped variable which will store the variable's value. Used only with name-from-attribute. |
| variable-class | The class of the variable. Default is java.lang.String. |
| declare | Indicates whether the variable is declared in the calling JSP page or tag file. Default is true. Not entirely clear what this means! |
| scope | The variable's scope; possible values are AT_BEGIN, AT_END and NESTED. NESTED is the default. |
| description | A description for the variable. |
The variable value is set by using setAttribute() on the jspContext object.
Here's an example, which uses the default NESTED setting for the scope.
That means that the variable values will be available only within the body of the tag.
The <doBody> tag at the end indicates that the tag body should be rendered,
and the variable values will be available then.
<%@ tag import="java.util.*" %>
<%@ attribute name="cityName" required="true" %>
<%@ variable name-given="province" %>
<%@ variable name-given="population" variable-class="java.lang.Integer" %>
<%
if ("Toronto".equals(cityName)) {
jspContext.setAttribute("province", "Ontario");
jspContext.setAttribute("population", new Integer(2553400));
}
else if ("Montreal".equals(cityName)) {
jspContext.setAttribute("province", "Quebec");
jspContext.setAttribute("population", new Integer(2195800));
}
else {
jspContext.setAttribute("province", "Unknown");
jspContext.setAttribute("population", new Integer(-1));
}
%>
<jsp:doBody/>
And the JSP page:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %>
<html>
<head>
</head>
<body>
<% pageContext.setAttribute("cityName", "Montreal"); %>
<util:lookup cityName="${cityName}">
${cityName}'s province is ${province}.
${cityName}'s population is approximately ${population / 1000000} million.
</util:lookup>
</body>
</html>
This is a bit less wordy using JSTL:
<%@ tag import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ attribute name="cityName" required="true" %>
<%@ variable name-given="province" %>
<%@ variable name-given="population" %>
<c:choose>
<c:when test="cityName eq 'Toronto'>
<c:set var="province" value="Ontario"/>
<c:set var="population" value="2553400"/>
</c:when>
<c:when test="cityName eq 'Montreal'>
<c:set var="province" value="Quebec"/>
<c:set var="population" value="2195800"/>
</c:when>
<c:otherwise>
<c:set var="province" value="Unknown"/>
<c:set var="population" value="-1"/>
</c:otherwise>
</c:choose>
%>
<jsp:doBody/>
and the JSP:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
</head>
<body>
<c:set var="cityName" value="Montreal"/>
<util:lookup cityName="${cityName}">
${cityName}'s province is ${province}.
${cityName}'s population is approximately ${population / 1000000} million.
</util:lookup>
</body>
</html>
Or let's do it XML! This would be lookup2.tagx:
<?xml version='1.0'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:directive.tag import="java.util.*"/>
<jsp:directive.taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"/>
<jsp:directive.attribute name="cityName" required="true"/>
<jsp:directive.variable name-given="province"/>
<jsp:directive.variable name-given="population"/>
<c:choose>
<c:when test="cityName eq 'Toronto'>
<c:set var="province" value="Ontario"/>
<c:set var="population" value="2553400"/>
</c:when>
<c:when test="cityName eq 'Montreal'>
<c:set var="province" value="Quebec"/>
<c:set var="population" value="2195800"/>
</c:when>
<c:otherwise>
<c:set var="province" value="Unknown"/>
<c:set var="population" value="-1"/>
</c:otherwise>
</c:choose>
</jsp:root>
and the JSP:
<?xml version='1.0'?>
<jsp:root version='2.0'
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:util="urn:jsptagdir:/WEB-INF/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<jsp:directive.page contentType="text/html"/>
<html>
<head>
</head>
<body>
<c:set var="cityName" value="Montreal"/>
<util:lookup cityName="${cityName}">
${cityName}'s province is ${province}.
${cityName}'s population is approximately ${population / 1000000} million.
</util:lookup>
</body>
</html>
</jsp:root>
The scope is a bit hard to understand. Here are the interpretations:
| EVENT | NESTED | AT_BEGIN | AT_END |
| beginning of tag file | save | nothing | nothing |
| before any fragment | tag to page | tag to page | nothing |
| after any fragment | nothing | nothing | nothing |
| end of tag file | restore | tag to page | tag to page |
Save/restore means that the value of the variable from the page is saved on entry to tag
and the restored afterwards. Consequently, any changes made inside the tag are not
visible to the the page. The actions of the tag therefore apply only within the tag,
which is suitable for the NESTED scope.
Tag to page means that the value of the variable inside the tag is copied to the
page's copy of the variable. Consequently, for AT_END scope, the tag sees the initial
value of the variable from the page, and then exports the value back to the page at
the end. For the AT_BEGIN scope, the tag observes an uninitialized value for the
variable, and exports it back to the page at the end.
Is that any clearer?
Dynamic attributes
The tag directive can include a dynamic-attributes attribute, which gives the name of
a map which stores all of the tag attributes which are not explicitly declared.
Here's an example of how it can be used.
First, the tag file (tabular.tag), which shows how to receive the attributes
via Java and using JSTL and expressions:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ tag import="java.util.*" %>
<%@ tag dynamic-attributes="attributes" %>
<%@ attribute name="title" %>
<%
out.println(jspContext.getAttribute("title"));
out.println("<table border='1'>");
Map m = (Map) jspContext.getAttribute("attributes");
for (Iterator i = m.keySet().iterator(); i.hasNext(); ) {
Object key = i.next();
Object val = m.get(key);
out.println("<tr>");
out.println("<td>" + key + "</td>");
out.println("<td>" + val + "</td>");
out.println("</tr>");
}
out.println("</table>");
%>
${title}
<table border='1'>
<c:forEach var="item" items="${attributes}">
<tr><td>${item.key}</td><td>${item.value}</td></tr>
</c:forEach>
</table>
And the jsp page:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %>
<html>
<head>
</head>
<body>
<util:tabular title="Dynamic Attributes!" name="Fred" wife="Wilma" friend="Barney" lover="Betty"/>
</body>
</html>
Which produces two copies of the following:
Dynamic Attributes!
| wife | Wilma |
| friend | Barney |
| name | Fred |
| lover | Betty |
JSP 2.0 Tags
Introduction
The second new mechanism in JSP 2.0 is a simplified approach to writing
tags using Java classes.
It's based on the 'SimpleTag' interface, which is not even in the same
class hierarchy as the tag classes from JSP 1.0.
It also uses the 'JspFragment' class to represent sections of a JSP page.
The implementation of tag files uses the JSP 2.0 tag approach; a JSP 2.0 tag
is generated from the tag file.
The JSP 1.0 approach is similar to event-driven programming - the JSP code
reacts to tag start, end and body events.
The JSP 2.0 approach is more top-down: the tag class has a single method,
doTag(), which is called when the tag is encountered, and the body is
presented to the tag class as a JspFragment.
To evaluate the body, the JspFragment class provides an invoke() method.
For iterating over the body, the tag class just calls invoke() in a loop.
invoke() takes a writer as parameter; you can supply a writer if you want
to capture the output, or pass null to have it write to the default stream.
Here's the SimpleTag interface:
public interface SimpleTag extends JspTag {
public void doTag()throws JspException, java.io.IOException;
public void setParent(JspTag parent);
public JspTag getParent();
public void setJspContext(JspContext pc);
public void setJspBody(JspFragment jspBody);
}
JspTag was introduced in 2.0 as a base interface for Tag and SimpleTag.
It defines no methods.
When implementing a simple tag, one typically derives from SimpleTagSupport.
It provides getJspBody() and findAncestorWithClass() methods.
When the doTag() method is called, setters for all attributes, and the setJspBody()
method (if necessary) will have been called.
The entire processing of the tag therefore takes place in doTag().
The JspContext object replaces the page context object used in JSP 1.0 tags.
The idea is that this object is not dependent on http servlet classes, making
the approach more generic.
The body is available to the tag as a JspFragment, but it is also possible to
define attributes as fragments.
In this case the setter method must accept a JspFrament object, and the
attribute must be declared as a fragment in the descriptor file.
This approach is useful when the tag needs to display different template
text depending on runtime conditions.
Example
Here's a rather complicated example.
The idea is to build a table of primes, where the background color
of each row indicates whether the prime is even, or of the forms 4k+1
or 4k+3.
This is interesting (to some), since Fermat proved that all primes of
the form 4k+1 can be written as the sum of two squares, while all primes
of the form 4k+3 cannot be so written.
That's interesting, isn't it?
The JSP page, test-fragment.jsp, appears below.
Note how all of the template text that builds the table is contained
in the JSP page.
The conditional parts, which define the background color of the row,
depending on the prime in question, appear as attributes of the
custom tag.
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %>
<html>
<head>
<title>JSP Test Tag</title>
</head>
<body>
<asitl:primes>
<jsp:attribute name="count">
10
</jsp:attribute>
<jsp:attribute name="evens">
<tr bgcolor='lightgreen'><td>${index}</td><td>${value}</td></tr>
</jsp:attribute>
<jsp:attribute name="odds1">
<tr bgcolor='cyan'><td>${index}</td><td>${value}</td></tr>
</jsp:attribute>
<jsp:attribute name="odds3">
<tr bgcolor='pink'><td>${index}</td><td>${value}</td></tr>
</jsp:attribute>
<jsp:body>
<table border='1' cellpadding='3'>
<tr><th>Position</th><th>Prime</th><tr>
${content}
</table>
</jsp:body>
</asitl:primes>
</body>
</html>
The output:
| Position | Prime |
|---|---|
| 1 | 2 |
| 2 | 3 |
| 3 | 5 |
| 4 | 7 |
| 5 | 11 |
| 6 | 13 |
| 7 | 17 |
| 8 | 19 |
| 9 | 23 |
| 10 | 29 |
The descriptor file:
<?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/web-jsptaglibrary_2_0.xsd'
version='2.0'>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<uri>http://www.alethis.net/tags/asitl</uri>
...
<tag>
<name>primes</name>
<tag-class>net.alethis.tags.PrimesTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>count</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>evens</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<fragment>true</fragment>
</attribute>
<attribute>
<name>odds1</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<fragment>true</fragment>
</attribute>
<attribute>
<name>odds3</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<fragment>true</fragment>
</attribute>
<variable>
<name-given>index</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
<variable>
<name-given>value</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
<variable>
<name-given>content</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope>
</variable>
</tag>
</taglib>
The implementation class, PrimesTag.java.
Note how the output from the attribute fragments is captured,
while the output from the body fragment is just sent to the standard output writer.
package net.alethis.tags;
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class PrimesTag extends SimpleTagSupport {
private String countString;
private JspFragment evensFragment;
private JspFragment odds1Fragment;
private JspFragment odds3Fragment;
public void setCount(String s) {
countString = s;
}
public void setEvens(JspFragment f) {
evensFragment = f;
}
public void setOdds1(JspFragment f) {
odds1Fragment = f;
}
public void setOdds3(JspFragment f) {
odds3Fragment = f;
}
public void doTag() throws JspException {
try {
int count = Integer.parseInt(countString);
StringWriter sw = new StringWriter();
int lastPrime = 0;
int total = 0;
for (int i = 1; total < count; i++) {
int p = nextPrime(lastPrime);
total++;
getJspContext().setAttribute("index", String.valueOf(total));
getJspContext().setAttribute("value", String.valueOf(p));
if (p % 2 == 0) {
evensFragment.invoke(sw);
}
else if (p % 4 == 1) {
odds1Fragment.invoke(sw);
}
else if (p % 4 == 3) {
odds3Fragment.invoke(sw);
}
lastPrime = p;
}
getJspContext().setAttribute("content", sw.toString());
getJspBody().invoke(null);
}
catch (Exception e) {
throw new JspTagException("error in RepeatTag: " + e.getMessage());
}
}
private int nextPrime(int start) {
for (int k = start + 1; ; k++) {
if (k < 2) {
continue;
}
boolean isPrime = true;
int sqrt = (int) Math.sqrt(k);
for (int m = 2; m <= sqrt; m++) {
if (k % m == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
return k;
}
}
}
}
The war file structure:
test-primes.jsp
WEB-INF
asitl.tld
classes
net
alethis
tags
PrimesTag.class