Advanced Features of JSP Custom Tag Libraries
Pages: 1, 2
Nested tags
Nested tags are another powerful feature of tag libraries. You can
use nested tags whether the tag implements the Tag or BodyTag
interface. When tags are nested, it's possible to obtain a reference
to the parent class by using the findAncestorWithClass
method. By casting the reference to the parent class, the nested tag
handler can call methods in the parent class. Another use for the
findAncestorWithClass is to determine what type of tag in
which another tag might be nested. It is likely, when building more
complicated systems, that a tag handler might need to act differently
depending on what the outer tag is. The tag handler can determine
what action to take based on the return value of the
findAncestorWithClass. Nested tags really only effect the
tag handler, so I will not reiterate TLD and JSP code here.
The following sample demonstrates the concepts we just discussed:
public class OuterTag1 extends TagSupport {
…
public void setMyValue(MyClass arg) { ... }
public MyClass getMyValue() { ... }
}
public class OuterTag2 extends TagSupport {
…
public void setSomeOtherValue(MyClass arg) { ... }
public MyClass getSomeOtherValue() { ... }
}
public class InnerTag extends BodyTagSupport {
public int doStartTag() throws JspTagException {
OuterTag1 parent1 =
(OuterTag1)findAncestorWithClass(this, OuterTag1.class);
OuterTag2 parent2 =
(OuterTag2)findAncestorWithClass(this, OuterTag2.class);
if (parent1 != null) {
parent1.setMyValue(...);
}
if (parent2 != null){
// make a different method call
MyClass value = parent2.getSomeOtherValue(…);
// Take a different action here for tag
// processing…
// do something with the value
}
return(EVAL_BODY_TAG);
}
...
}
TEI
The Tag Extra Info (TEI) Class is used to enable scripting
variables and also to perform attribute validation. The TEI is a Java
class that extends the TagExtraInfo class. The method
used is the getVariableInfo; it takes a TagData parameter
and returns an array of VariableInfo. The TagData
parameter contains the name-value pairs of attributes which can be
used for scripting variables.
The main purpose of the getVariableInfo method is to
create an array of VariableInfo objects. You create one VariableInfo
object for each scripting variable that you will be using. When
new'ing a VaribleInfo object the following are
defined:
- String specifying the scripting variable name;
- String specifying the class for the scripting variable;
- boolean indicating whether the constructor should declare a new variable;
- int specifying the scope for the scripting variable.
The scope of variables in tags can be defined as one of the following:
AT_BEGIN-- the variable is available in the body of the tag an in the remainder of the JSP;NESTED-- the variable is available in the body of the tag;AT_END-- the variable is available in the remainder of the JSP page
One more aspect of TEI worth mentioning is that it can be used for
attribute validation. A TEI class can optionally override the
isValid method to implement tag-specific attribute
validation. A TagData instance is passed to the
isValid method at JSP translation time. Therefore if an
attribute allows runtime evaluation, the isValid method
can't perform a validation on it.
Let's step through an example that describes the TEI class, the handler, and the JSP that would use the tag. I will skip over the TLD file since it is just a matter of using the <teiclass>classname<teiclass> within the tag definition.
The scripting variables loopCounter,
loopCounter2, and loopCounter3 are defined
in the following TEI class to demonstrate the differences that scope
makes. It is possible that the information necessary to construct the
Variable info is passed in the tag attributes. In that case, the
values would be retrieved with the
data.getAttributeString method.
public class IterationTEI extends TagExtraInfo {
...
public VariableInfo[] getVariableInfo(TagData data) {
VariableInfo[] scriptVars = new VariableInfo[3];
// We are telling the constructor not to create
// a new variable since we will define it in our JSP
// loopCounter will be available in the body and
// remainder of the JSP
scriptVars[0] = new VariableInfo("loopCounter",
"java.lang.String",
false,
VariableInfo.AT_BEGIN);
// loopCounter2 will be available after the tag
//and for the remainder of the JSP
scriptVars[1] = new VariableInfo("loopCounter2",
"java.lang.String",
true,
VariableInfo.AT_END);
// loopCounter3 will be available in the tag only
scriptVars[1] = new VariableInfo("loopCounter3",
"java.lang.String",
true,
VariableInfo.NESTED);
return scriptVars;
}
}
Instead of defining the entire Java source for this class for the tag handler (we've already done this for other handlers), I will just include the pertinent methods for this example.
public class IterationTag extends BodySupport{
…
define any attribute setter methods
…
public int doStartTag() throws JspException{
// The TEI file defines loopCounter as
//AT_BEGIN and loopCounter3 as NESTED scope
// so we define them here. They can also be
// defined in doInitBody and modified/reset
// in doAfterBody
pageContext.setAttribute("loopCounter","0");
pageContext.setAttribute("loopCounter3","3");
return EVAL_BODY_TAG;
}
public int doEndTag() throws JspException{
…
do other processing
…
// The TEI file defines loopCounter2 as AT_END
// so we define it here
pageContext.setAttribute("loopCounter2","2");
return EVAL_BODY_TAG;
}
}
The JSP using these scripting variables would look like:
<%@ taglib uri="/oreillySample.tld" prefix="sample" %>
<%--
We told the TEI not to create this variable
so we are creating it here
--%>
<% String loopCounter; %>
<sample:iteration >
<%-- loopCounter is available starting in the body --%>
The value of loopCounter1 = <% loopCounter %><br />
<%-- loopCounter3 is only available in the body --%>
The value of loopCounter3 = <% loopCounter3 %><br />
</sample:iteration>
<%-- loopCounter is still available --%>
The value of loopCounter1 = <% loopCounter %><br />
<%-- loopCounter2 is available after the tag --%>
The value of loopCounter2 = <% loopCounter2 %><br />
Cooperating tags
Tags can also cooperate with each other by means of shared objects.
In the following example, tag1 creates a named object,
obj1, which is then reused by tag2. The
convention encouraged by the JSP specification is that a tag with an
attribute id creates and names an object, and the object is then
referenced by other tags with an attribute named
name.
<sample:tag1 id="obj1" attr1="value" />
<sample:tag2 name="obj1" />
This is also a useful feature when nesting tags since all objects created by the enclosing tag are available to all inner tags.
Conclusion
If you've gotten this far, then you should be ready to design and implement custom tag libraries, using all of their power in ways that work for your particular situation.
Sue Spielman is an associate editor for ONJava.com, covering JSP and Servlets technologies. She is also President and Senior Consulting Engineer for Switchback Software LLC.
Return to ONJava.com.