Chapter 7

Writing Custom Tags

In Chapter 6, “JSTL,” you learned how to use custom tags in JSTL. The libraries in JSTL provide tags for solving common problems, but if your problems are not so common, you will have to write your own custom tags by extending a member of the javax.servlet.jsp.tagext package. This chapter teaches you how.

Custom Tag Overview

Using JSP standard actions to access and manipulate JavaBeans was the first attempt to allow separation of presentation (HTML) and business logic implementation (Java code). However, standard actions were not powerful enough that using them alone developers would often still have to resort to Java code in JSP pages. For example, standard actions cannot be used to iterate over a collection the way the JSTL forEach tag can.

In recognition of the imperfection of JavaBeans as a solution to separation of presentation and business logic, JSP 1.1 defined custom tags. Custom tags offer benefits that are not present in JavaBeans. Among others, custom tags have access to JSP implicit objects and can have attributes.

While using custom tags enables you to write script-free JSP pages, custom tags in JSP 1.1 and 1.2, called the classic custom tags, were notoriously hard to write. JSP 2.0 added two new features that made writing them easier. The first feature was a new interface called SimpleTag, which is discussed in this chapter. The second feature was a mechanism to write custom tags as tag files. Tag files are explained in Chapter 8, “Tag Files.”

The implementation for a custom tag is called a tag handler, and simple tag handlers refer to tag handlers that implement SimpleTag. In this chapter, you’ll explore how custom tags work and how to write tag handlers. Only simple tag handlers will be discussed as there is no reason to write classic tag handlers anymore.

In addition to being easier to write than classic tag handlers, simple tag handlers, unlike classic tag handlers, may not be cached by the JSP container. However, this does not mean simple tag handlers are slower than their predecessors. The JSP specification authors wrote in section JSP.7.1.5 of the specification, “Initial performance metrics show that caching a tag handler instance does not necessarily lead to greater performance, and to accommodate such caching makes writing portable tag handlers difficult and makes the tag handler prone to error.”

Simple Tag Handlers

The designers of JSP 2.0 realized how complex it was to write custom tags and tag handlers in JSP 1.1 and JSP 1.2. As such, in JSP 2.0 they added a new interface to the javax.servlet.jsp.tagext package: SimpleTag. Tag handlers implementing SimpleTag are called simple tag handlers, and tag handlers implementing Tag, IterationTag, or BodyTag are called classic tag handlers.

Simple tag handlers have a simpler lifecycle and are easier to write than classic tag handlers. The SimpleTag interface has one method in regard to tag invocation that will be called only once: doTag. Business logic, iteration, and body manipulation are to be written here. A body in a simple tag handler is represented by an instance of the JspFragment class. JspFragment is discussed in the subsection, “JspFragment” towards the end of this section.

The lifecycle of a simple tag handler is as follows:

1. The JSP container creates an instance of a simple tag handler by calling its no-argument constructor. Therefore, a simple tag handler must have a no-argument constructor.

2. The JSP container calls the setJspContext method, passing a JspContext object: The most important method of JspContext is getOut, which returns a JspWriter for sending response to the client. The signature of the setJspContext method is as follows.

      public void setJspContext(JspContext jspContext)

In most cases, you will need to assign the passed in JspContext to a class variable for later use.

3. If the custom tag representing the tag handler is nested within another tag, the JSP container calls the setParent method. This method has the following signature:

public void setParent(JspTag parent)

4. The JSP container calls the setter for each attribute defined for this tag.

5. If a body exists, the JSP container calls the setJspBody method of the SimpleTag interface, passing the body as a JspFragment. The JSP container will not call this method if the tag does not have a body.

6. The JSP container calls the doTag method. All variables are synchronized when the doTag method returns.

The javax.servlet.jsp.tagext package also includes a support class for SimpleTag: SimpleTagSupport. SimpleTagSupport provides default implementations for all methods in SimpleTag and serves as a convenient class that you can extend to write a simple tag handler. The getJspContext method in the SimpleTagSupport class returns the JspContext instance passed by the JSP container when it calls the setJspContext method of the SimpleTag interface.

SimpleTag Example

This section presents the customtagsdemo application, an example of a simple tag handler. There are two steps required in creating a custom tag, writing a tag handler and registering the tag. Both steps are explained below.

Note that if you are not using NetBeans, you need the Servlet API and JSP API packages in your build path to compile a tag handler. If you’re using Tomcat, you can find the jar files containing the APIs in Tomcat’s lib directory (the servlet-api.jar fie and the jsp-api.jar file).

The application directory structure for customtagsdemo is given in Figure 7.1. The custom tag consists of a tag handler (located under WEB-INF/classes) and a descriptor (the mytags.tld file under WEB-INF). Figure 7.1 also includes a JSP file for testing the custom tag.

Figure 7.1: The customtags application directory structure

Writing the Tag Handler

Listing 7.1 shows the MyFirstTag class, an implementation of SimpleTag.

Listing 7.1: The MyFirstTag class

package customtagsdemo;
import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTag;

public class MyFirstTag implements SimpleTag {
    JspContext jspContext;

    public void doTag() throws IOException, JspException {
        System.out.println("doTag");
        jspContext.getOut().print("This is my first tag.");
    }

    public void setParent(JspTag parent) {
        System.out.println("setParent");
    }

    public JspTag getParent() {
        System.out.println("getParent");
        return null;
    }

    public void setJspContext(JspContext jspContext) {
        System.out.println("setJspContext");
        this.jspContext = jspContext;
    }

    public void setJspBody(JspFragment body) {
        System.out.println("setJspBody");
    }
}

The MySimpleTag class has a jspContext variable of type JspContext. The setJspContext method assigns the JspContext it receives from the JSP container to this variable. The doTag method uses the JspContext to obtain a JspWriter. You call the print method on the JspWriter to output the String “This is my first tag”.

Registering the Tag

Before a tag handler can be used in a JSP page, it must be registered in a tag library descriptor, an XML file with tld extension. The tag library descriptor for this example is called mytags.tld and is given in Listing 7.2. This file must be saved in the WEB-INF directory.

Listing 7.2: The tag library descriptor (mytags.tld file)

<?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_1.xsd"
    version="2.1">
    
    <description>
         Simple tag examples
    </description>
    <tlib-version>1.0</tlib-version>
    <short-name>My First Taglib Example</short-name>
    <tag>
        <name>firstTag</name>
        <tag-class>customtagsdemo.MyFirstTag</tag-class>
        <body-content>empty</body-content>
    </tag>
</taglib>

The main element in the tag library descriptor is the tag element, which describes the tag. It contains a name element and a tag-class element. name specifies the name that will be used to refer to this tag. tag-class specifies the fully-qualified name of the tag handler. A tag library descriptor may contain multiple tag elements.

In addition, you may have other elements in a tag library descriptor. The description element is used to provide a description of the tags described in this descriptor. The tlib-version element specifies the version of the custom tags and the short-name element provides a short name for the tags.

Using the Tag

To use a custom tag, you use the taglib directive. The uri attribute of the taglib directive may reference a relative path or an absolute path. In this example, a relative path is used. However, if you’re using a tag library that is packaged in a jar file, you’ll use an absolute path. The section “Distributing Custom Tags” later in this chapter shows you how to package a custom tag library for easy distribution.

To test the firstTag custom tag, use the firstTagTest.jsp page in Listing 7.3.

Listing 7.3: The firstTagTest.jsp

<%@ taglib uri="/WEB-INF/mytags.tld" prefix="easy"%>
<!DOCTYPE html>
<html>
<head>
    <title>Testing my first tag</title>
</head>
<body>
Hello!!!!
<br/>
<easy:firstTag></easy:firstTag>
</body>
</html>

You can invoke the firstTagTest.jsp page with this URL:

http://localhost:8080/customtagsdemo/firstTagTest.jsp

When you invoke the firstTagTest.jsp page, the JSP container calls the tag handler’s setJspContext method. Since the tag in firstTagTest.jsp does not have a body, the JSP container doesn’t call the setJspBody method before calling the doTag method. In your console, you can see the following output:

setJspContext
doTag

Note that the JSP container does not call the tag handler’s setParent method either because the simple tag is not nested within another tag.

Handling Attributes

Tag handlers that implement SimpleTag or extend SimpleTagSupport can have attributes. Listing 7.4 presents a tag handler called DataFormaterTag that formats comma-delimited items into an HTML table. You pass two attributes to this tag, header and items. The header attribute value will become the header of the table. For example, if you pass “Cities” as the value for the header attribute and “London,Montreal” as the value for the items attribute, you’ll get the following output:

<table style="border:1px solid green">
<tr><td><b>Cities</b></td></tr>
<tr><td>London</td></tr>
<tr><td>Montreal</td></tr>
</table>

Listing 7.4: The DataFormatterTag class

package customtagsdemo;
import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class DataFormatterTag extends SimpleTagSupport {
    private String header;
    private String items;

    public void setHeader(String header) {
        this.header = header;
    }

    public void setItems(String items) {
        this.items = items;
    }

    public void doTag() throws IOException, JspException {
        JspContext jspContext = getJspContext();
        JspWriter out = jspContext.getOut();

        out.print("<table style='border:1px solid green'>
" 
                + "<tr><td><span style='font-weight:bold'>"
                + header + "</span></td></tr>
");
        String[] tokens = items.split(",");
        for (String token : tokens) {
            out.print("<tr><td>" + token + "</td></tr>
");
        }
        out.print("</table>");
    }
}

The DataFormatterTag class provides two setters to receive attributes: setHeader and setItems. The doTag method does the rest.

The doTag method first obtains the JspContext passed by the JSP container by calling the getJspContext method:

JspContext jspContext = getJspContext();

Then, it calls the getOut method on the JspContext instance to obtain a JspWriter it can use to write response to the client:

JspWriter out = jspContext.getOut();

Next, the doTag method uses the String class’s split method to parse the items attribute and turn each item to a table row:

    out.print("<table style='border:1px solid green'>
" 
            + "<tr><td><span style='font-weight:bold'>" 
            + header + "</span></td></tr>
");
    String[] tokens = items.split(",");
    for (String token : tokens) {
        out.print("<tr><td>" + token + "</td></tr>
");
    }
    out.print("</table>");

To use the DataFormatterTag tag handler, you must register it using the tag element in Listing 7.5. Simply add it to the mytags.tld file used in the previous example.

Listing 7.5: Registering dataFormatter tag

<tag>
    <name>dataFormatter</name>
    <tag-class>customtagsdemo.DataFormatterTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>header</name>
        <required>true</required>
    </attribute>
    <attribute>
        <name>items</name>
        <required>true</required>
    </attribute>
</tag>

You can then use the dataFormatterTagTest.jsp page in Listing 7.6 to test the tag handler.

Listing 7.6: The dataFormatterTagTest.jsp Page

<%@ taglib uri="/WEB-INF/mytags.tld" prefix="easy"%>
<!DOCTYPE html>
<html>
<head>
    <title>Testing DataFormatterTag</title>
</head>
<body>
<easy:dataFormatter header="States" 
    items="Alabama,Alaska,Georgia,Florida"
/>

<br/>
<easy:dataFormatter header="Countries">
    <jsp:attribute name="items">
        US,UK,Canada,France
    </jsp:attribute>
</easy:dataFormatter>
</body>
</html>

Note that the JSP page in Listing 7.6 uses the dataFormatter tag twice, passing attributes in two different ways, in a tag attribute and by using the attribute standard action. You can invoke dataFormatterTagTest.jsp using this URL:

http://localhost:8080/customtagsdemo/dataFormatterTagTest.jsp

Figure 7.2 shows the result of invoking dataFormatterTagTest.jsp.

Figure 7.2: Using attributes with SimpleTag

Manipulating the Tag Body

With SimpleTag, you can manipulate the tag body via the JspFragment passed by the JSP container. The JspFragment class represents a portion of JSP code that can be invoked zero or more time. The definition of the JSP fragment must not contain scriptlets or scriptlet expressions. It can only contain template text and JSP action elements.

The JspFragment class has two methods: getJspContext and invoke. The signatures of the methods are as follows:

public abstract JspContext getJspContext()
public abstract void invoke(java.io.Writer writer) 
        throws JspException, java.io.IOException

The getJspContext method returns the JspContext associated with this JspFragment. You call the invoke method to execute the fragment (the tag body) and directs all output to the given Writer. If you pass null to the invoke method, the output will be directed to the JspWriter returned by the getOut method of the JspContext associated with this fragment.

Consider the SelectElementTag class in Listing 7.7. You use this tag handler to send an HTML select element with the following format:

<select>
<option value="value-1">text-1</option>
<option value="value-2">text-2</option>
...
<option value="value-n">text-n</option>
</select>

In this case, the values are country names in the String array countries.

Listing 7.7: SelectElementTag

package customtagsdemo;
import java.io.IOException;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class SelectElementTag extends SimpleTagSupport {
    private String[] countries = {"Australia", "Brazil", "China" };

    public void doTag() throws IOException, JspException {
        JspContext jspContext = getJspContext();
        JspWriter out = jspContext.getOut();
        out.print("<select>
");
        for (int i=0; i<3; i++) {
            getJspContext().setAttribute("value", countries[i]);
            getJspContext().setAttribute("text", countries[i]);
            getJspBody().invoke(null);
        }
        out.print("</select>
");
    }
}

Listing 7.8 shows the tag element used to register SelectElementTag and maps it to the tag called select. Again, add this element to the mytags.tld file used in the previous examples.

Listing 7.8: Registering SelectElementTag

<tag>
    <name>select</name>
    <tag-class>customtagsdemo.SelectElementTag</tag-class>
    <body-content>scriptless</body-content>
</tag>

Listing 7.9 presents a JSP page (selectElementTagTest.jsp) that uses SelectElementTag.

Listing 7.9: The selectElementTagTest.jsp Page

<%@ taglib uri="/WEB-INF/mytags.tld" prefix="easy"%>
<!DOCTYPE html>
<html>
<head>
    <title>Testing SelectElementFormatterTag</title>
</head>
<body>
<easy:select>
    <option value="${value}">${text}</option>
</easy:select>
</body>
</html>

Note that the select tag is used by passing a body in the following format:

<option value="${value}">${text}</option>

The value and text attributes get their values from each invocation of the JspFragment in the doTag method of the SelectElementTag tag handler:

    for (int i=0; i<3; i++) {
        getJspContext().setAttribute("value", countries[i]);
        getJspContext().setAttribute("text", countries[i]);
        getJspBody().invoke(null);
    }

You can invoke selectElementTagTest.jsp using this URL:

http://localhost:8080/customtagsdemo/selectElementTagTest.jsp

Figure 7.3 shows the result.

Figure 7.3: Using JspFragment

If you view the source in your web browser, you’ll see the following:

<select>
  <option value="Australia">Australia</option>
  <option value="Brazil">Brazil</option>
  <option value="China">China</option>
</select>

Writing EL Functions

Chapter 5, “The Expression Language” discussed the JSP expression language (EL) and mentioned that you can write functions that you can invoke by using an EL expression. Writing EL functions is discussed in this chapter rather than in Chapter 5 because it involves the use of a tag library descriptor.

Basically, you can write an EL function by following these two steps:

1. Create a public class containing static methods. Each static method represents a function. This class does not have to implement an interface or extend a class. Deploy this class as you would any other class. This class must be saved to the WEB-INF/classes directory or a directory under it.

2. Register the function in a tag library descriptor using the function element.

The function element must be placed directly under the taglib element and can have the following sub-elements:

  • description. An optional tag-specific information.
  • display-name. A short name to be displayed by XML tools.
  • icon. An optional icon element that can be used by XML tools.
  • name. A unique name for this function.
  • function-class. The fully-qualified name of the Java class that implements the function.
  • function-signature. The signature of the static Java method representing the function.
  • example. An optional informal description of an example that uses this function.
  • function-extension. Zero or more extensions that provide extra information about this function, used by XML tools.

To use a function, you employ the taglib directive with its uri attribute pointing to the tag library descriptor and a prefix indicating the prefix to be used. You then call the function by using the following syntax in your JSP page:

${prefix:functionName(parameterList)}

As an example, consider the the StringFunction class in Listing 7.10. The class encapsulates a static method named reverseString.

Listing 7.10: The reverseString method in the StringFunction class

package function;
public class StringFunction {
    public static String reverseString(String s) {
        return new StringBuffer(s).reverse().toString();
    }
}

Listing 7.11 shows the functiontags.tld descriptor that contains a function element that describes the reverseString function. This TLD must be saved in the WEB-INF directory of the application that is using it.

Listing 7.11: The functiontags.tld file

<?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_1.xsd"
    version="2.1">
    
    <description>
         Function tag examples
    </description>
    <tlib-version>1.0</tlib-version>
    <function>
        <description>Reverses a String</description>
        <name>reverseString</name>
        <function-class>function.StringFunction</function-class>
        <function-signature>
            java.lang.String reverseString(java.lang.String)
        </function-signature>
    </function>	   
</taglib>

Listing 7.12 shows the reverse.jsp page for testing the EL function.

Listing 7.12: Using the EL Function

<%@ taglib uri="/WEB-INF/functiontags.tld" prefix="f"%>
<!DOCTYPE html>
<html>
<head>
    <title>Testing reverseString function</title>
</head>
<body>
${f:reverseString("Hello World")}
</body>
</html>

You can invoke reverse.jsp using the following URL:

http://localhost:8080/customtagsdemo/reverse.jsp

Upon invoking the JSP page, you’ll see “Hello World” in reverse.

Distributing Custom Tags

You can package your custom tag handlers and tag library descriptor in a jar file so that you can distribute it for others to use, just like the JSTL. In this case, you need to include all the tag handlers and the tld file that describes them. In addition, you need to specify an absolute URI in a uri element in the descriptor.

For example, application mytags accompanying this book packages the tag and the descriptor in mytags into a mytags.jar file. The content of the jar file is shown in Figure 7.4.

Figure 7.4: The mytags.jar file

Listing 7.13 presents the functiontags.tld file. Note that a uri element has been added to the descriptor. The value of the element is http://example.com/taglib/function.

Listing 7.13: The functiontags.tld file of the packaged custom tag

<?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_1.xsd"
    version="2.1">
    <description>
         Function tag examples
    </description>
    <tlib-version>1.0</tlib-version>

    <uri>http://example.com/taglib/function</uri>

    <function>
        <description>Reverses a String</description>
        <name>reverseString</name>
        <function-class>function.StringFunction</function-class>
	 <function-signature>
            java.lang.String reverseString(java.lang.String)
        </function-signature>
    </function>	   
</taglib>

To use the library in an application, you must copy the jar file to the WEB-INF/lib directory of the application. On top of that, any JSP page that uses the custom tag must specify the same URI as the one defined in the tag library descriptor.

The reverse2.jsp page in Listing 7.14 shows a JSP page that uses the custom tag.

Listing 7.14: The reverse2.jsp file in mytags

<%@ taglib uri="http://example.com/taglib/function" prefix="f"%>
<!DOCTYPE html>
<html>
<head>
    <title>Testing reverseString function</title>
</head>
<body>
${f:reverseString("Welcome")}
</body>
</html>

You can test the example by directing your browser to this URL.

http://localhost:8080/mytags/reverse2.jsp

Summary

You have seen in this chapter that custom tags are a better solution than JavaBeans to the issue of separation of presentation and business logic. To write a custom tag, you need to create a tag handler and register the tag in a tag library descriptor.

As of JSP 2.2, there are two types of tag handlers, classic tag handlers and simple tag handlers. The former implement the Tag, IterationTag, or BodyTag interface or extend one of the two support classes, TagSupport and BodyTagSupport. Simple tag handlers, on the other hand, implement the SimpleTag interface or extend SimpleTagSupport. Simple tag handlers are easier to write and have a simpler lifecycle than classic tag handlers. Simple tag handlers are the recommended type and this chapter presented a couple of simple tag examples. You can also distribute your custom tag library in a jar file for others to use.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.246.203