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

advertisement

AddThis Social Bookmark Button
JavaServer Pages, 3rd Edition

JSP 2.0: The New Deal, Part 4

by Hans Bergsten, author of JavaServer Pages, 3rd Edition
05/12/2004

In this final part of the "JSP 2.0: The New Deal" series, we look at two new features that make it much easier to develop custom tag libraries: tag files and the new simplified tag-handler Java API.

Developing Custom Actions as JSP Tag Files

JSP is intended to make it possible for people who are not Java gurus to write pages with dynamic content. Reusing a piece of dynamic, complex content between pages has been a bit of a pain until now. Say that you want to put a quick poll on a number of pages, with the question and answers picked up from variables. Prior to JSP 2.0, you had three options. You could copy and paste the poll form to every page where you wanted it. A better choice was to write a separate JSP page that generates the poll form, and include it in other pages with the <jsp:include> or <c:import> actions, but you could only pass it input parameters of type String, not a bean or a Map holding the answers. for instance. The third choice was to implement a custom action as a Java tag handler class, but then you'd need to know Java.

JSP 2.0 adds a fourth option: namely, developing a custom action as a tag file. A tag file is a plain text file where you use JSP elements for all dynamic parts, just as in a regular JSP page. It has the same purpose as a Java tag handler: to provide the logic for a custom action. The main differences between a tag file and a JSP page are that a tag file has a .tag filename extension, uses a tag directive instead of a page directive, and lets you declare input and output with a few new directives that are only valid in tag files.

Let's take a closer look. Here's a tag file named poll.tag that generates a quick poll form:

<%@ tag body-content="empty" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true" 
   type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Question: ${question}<br>
<form action="result.jsp" target="result">
   <input type="hidden" name="question" value="${question}">
   <input type="hidden" name="votesMapName" value="${votesMapName}">
   <input type="hidden" name="answersMapName" value="${answersMapName}">
   <c:forEach items="${answers}" var="a">
      <input type="radio" name="vote" value="${a.key}">${a.value}<br>
   </c:forEach>
   <input type="submit" value="Vote">
</form>

Related Reading

JavaServer Pages
By Hans Bergsten

At the top of the file, there's a tag directive. The tag directive is similar to the page directive you use in a JSP page; it declares general file characteristics. Here I use the body-content attribute to declare that an action element representing this tag file in a JSP page must be empty; i.e., the action element must not have a body. Instead of empty, you can use the values scriptless (the body can contain anything except scripting elements), or tagdependent (the container passes the body to the tag handler without evaluating it). If you've developed custom actions as Java classes, you probably recognize the body-content and the valid values from the Tag Library Descriptor (TLD) used to declare Java tag handlers. Because a tag file doesn't need to be declared in a TLD, the tag directive and other special tag file directives are used to provide the same type of information that the TLD provides to the JSP container.

The attribute directive that follows the tag directive in this example serves the same function as the TLD element with the same name: it declares valid custom action element attributes. The poll tag file accepts four attributes:

  • question: The poll question

  • answers: An application-scope Map with the poll answers, keyed by numbers.

  • votesMapName: The name of an application-scope variable for a Map holding the votes per answer as the value, keyed by the answer numbers.

  • answersMapName: The name of the application-scope variable holding the answers Map; i.e., the Map passed as the answers attribute value.

There's one attribute directive per action element attribute, with the name declared by the name attribute. All action element attributes in this example are required, as declared by the required attribute for each attribute directive. The answers attribute value must be a Map, which is declared by the type attribute. All other attributes must be of type String, which is the default type, so I don't specify the type attribute for them.

The rest of the tag file is plain old JSP. A taglib directive declares that the JSTL core library is used in the file, and the body of the file uses the attribute values (available to the tag file as page-scope variables) in an EL expression to write the question and a form with radio buttons for each answer. The answers and votes Map variable names and the question are encoded as hidden fields in the form so they get passed on to the page processing the vote, where they may be used like this:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
   <head>
      <title>Poll Results</title>
   </head>
   <body bgcolor="white">
      <c:set target="${applicationScope[param.votesMapName]}" 
        property="${param.vote}" 
        value="${applicationScope[param.votesMapName][param.vote] + 1}" />
    
      <p>
        Question: ${param.question}<br>
        <c:forEach items="${applicationScope[param.answersMapName]}"
          var="a">
          ${a.key}) ${a.value}: 
          ${applicationScope[param.votesMapName][a.key]}<br>
        </c:forEach>
      </p>
   </body>
</html>

It's a regular JSP page, using JSTL actions. It increments the value for the key matching the selected poll answer in the Map holding vote results, available in the application scope under the name provided through the votesMapName parameter. Next, it writes the question and iterates through the Map with poll answers, available in the application scope under the name provided by the answersMapName, and writes each answer along with the current number of votes for the answer.

Of more interest than how to process the poll votes is how to use the custom action implemented by the poll tag file in a JSP page. Here's an example:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>

<html>
   <head>
      <title>My Page</title>
   </head>
   <body bgcolor="white">
      <jsp:useBean id="myAnswers" scope="application" 
        class="java.util.TreeMap">
        <c:set target="${myAnswers}" property="1" value="Yes" />
        <c:set target="${myAnswers}" property="2" value="No" />
        <c:set target="${myAnswers}" property="3" value="Maybe" />
      </jsp:useBean>
      <jsp:useBean id="myVotes" scope="application"
        class="java.util.HashMap" />    
      ...
      <p>
        <my:poll question="Will you start using tag files?" 
          answers="${myAnswers}"
          answersMapName="myAnswers" votesMapName="myVotes" />
      </p>
      ...
   </body>
</html>

The first thing to notice is the taglib directive that declares the tag library holding the tag file. To use a tag file without creating a TLD, you must store the tag file somewhere under the WEB-INF/tags directory. The example poll.tag is stored in the WEB-INF/tags/mytags directory, and I use this directory name as the value of the taglib directive's tagdir attribute. This tell the container that all tag files found in this directory belong to the same tag library, and that the action elements for actions in this library are identified by the prefix specified by the taglib directive's prefix attribute; my, in this example. Alternatively, you can package tag files along with a TLD in a .jar file, and declare the library with the same kind of taglib directive that you use for a regular custom tag library; i.e., with a uri attribute instead of the tagdir.

In this example, the Map objects for answers and vote counts are created by <jsp:useBean> actions and populated with JSTL <c:set> actions, but you can, of course, create them any way you like (e.g., in a context listener or in a "plugin" class in an Apache Struts application). No matter how it is created, the tag file is invoked through a regular JSP custom action element. I use an EL expression that evaluates to the answers Map as the value of the answers attribute. Tag file attributes accept EL expressions by default, but you can declare that a static value must be specified with the rtexprvalue attribute of the attribute directive.

When you request this page, the JSP container processes it as usual, locating the implementation for the <my:poll> custom action element with the help of the element name prefix and the taglib directive. The container may process the tag file any way it wants; the Tomcat 5 container converts it into a Java tag handler class, compiles it, and executes it.

Pages: 1, 2

Next Pagearrow