4.4 Custom Iteration ActionsThe <c:forEach> and <c:forTokens> actions should be sufficient for most of your iteration needs, but you may find that you need a custom action that provides more functionality than those actions or a custom action that simplifies their use. JSTL provides support for two types of custom iteration actions:
Each of those types of custom actions is discussed in the following sections. Collaboration Custom ActionsCollaboration custom actions work in concert with existing iteration actions. Typically, collaboration custom actions provide functionality related to the status of an iteration; for example, a custom action that implements some functionality for the first round of an iteration.The <c:forEach> and <c:forTokens> actions are implemented with tag handlers that implement the LoopTag interface; the methods defined by that interface are listed in Table 4.3.
In Listing 4.5, represented by the preceding code fragment, we created the table headers during the first round of the iteration and created the rest of the table's data during the subsequent rounds of the iteration. That required us to differentiate between the first round of the iteration and the subsequent rounds, as illustrated by the preceding code fragment. That differentiation required us to use the <c:forTokens> varStatus attribute to access a status object and to use that status object with the <c:choose>, <c:when>, and <c:otherwise> actions. If we implement two custom actions, one that detects the first round of an iteration and another that detects all other rounds, we can significantly reduce that complexity, like this:
Listing 4.5 on page 168 that uses the custom actions described above. Listing 4.8 Using a Collaborative Custom Action
The tag handler for the <core-jstl:firstRound> custom action is listed in Listing 4.9.The tag handler shown in Listing 4.9 uses the findAncestorWithClass method from javax.servlet.jsp.tagext.TagSupport to obtain a reference to an enclosing action, such as <c:forEach> or <c:forTokens>, that implements the LoopTag interface. If no ancestor action fits that requirement, the preceding tag handler throws an exception. If the ancestor action is found, the tag handler accesses the iteration status by invoking that ancestor's getLoopStatus method, which returns an object whose type is LoopTagStatus. That status object is subsequently used to determine whether the current round of iteration is the first; if so, the body of the action is evaluated; if not, the action's body is skipped.The tag handler for the <core-jstl:notFirstRound> action is listed in Listing 4.10.The preceding tag handler is almost identical to the tag handler listed in Listing 4.9 except the preceding tag handler evaluates its body content if the current round of iteration is not the first round.NoteThe common functionality implemented by the two preceding tag handlers could be encapsulated in a base class, thereby reducing the amount of code that needs to be written and making maintenance of those tag handlers easier. In the interest of simplicity, the two preceding tag handlers do not share that common base class. Listing 4.9 WEB-INF/classes/tags/FirstRoundAction.java
The tag library containing the two custom actions used in Listing 4.8 on page 180 are declared in the tag library descriptor (TLD) listed in Listing 4.11.The preceding TLD declares the <core-jstl:firstRound> and <core-jstl:notFirstRound> actions and their associated tag handlers. That TLD is referenced with a taglib directive in Listing 4.8 on page 180. Iteration Custom ActionsIn addition to implementing collaboration custom actions as described in "Collaboration Custom Actions" on page 178, you can also implement custom actions that iterate by implementing the LoopTag interface. The easiest way to do that is to extend the LoopTagSupport class which implements the LoopTag interface and provides a number of protected variables and methods that greatly simplify implementing custom iteration actions. Table 4.4 lists the LoopTagSupport protected variables. Listing 4.10 WEB-INF/classes/tags/NotFirstRoundAction.java
The protected variables listed above give you direct access to the begin, end, step, var (itemId), and varStatus (statusID) attributes. You can also find out whether the begin, end, and step attributes were specified with the beginSpecified, endSpecified, and stepSpecified variables.The LoopTagSupport class defines three abstract methods that subclasses must implement; those methods are listed in Table 4.5.[5] [5] LoopTagSupport subclasses must implement those abstract methods if they are to be considered concrete classes. The methods listed in Table 4.5 are always called in the order they are listed in Table 4.5. The prepare method is called by the LoopTagSupport.doStartTag method before an iteration starts. Subsequently, the LoopTagSupport superclass calls the hasNext method (possibly more than once) for each round of the iteration. Finally, the next method, which returns the next object in the iteration, is invoked by the LoopTagSupport superclass. Listing 4.11 WEB-INF/core-jstl.tld
[a] All of the methods in this table are protected and can throw instances of JspTagException. Typically, the three methods listed in Table 4.5 are the only methods you will need to implement for your iteration custom actions. The LoopTagSupport class also provides a number of convenience methods, which are listed in Table 4.6.
[a] The last three methods in this table are protected and can throw instances of JspTagException. All the rest are public and do not throw exceptions. If you write enough iteration custom actions, you will probably find a use for all of the methods listed in Table 4.6 at one time or another, except for the setVar and setVarStatus methods, which are setter methods for the var and varStatus attributes, respectively.The following two sections show you how to implement to custom actions: one that iterates over integer values, and another that iterates over a data structure. Custom Actions That Iterate Over Integer ValuesRemember from our discussion in "The <c:forEach> Action" on page 154 that the <c:forEach> action can be used in two ways: to iterate over integer values or a data structure. The differentiator that determines how <c:forEach> is used is whether you specify a data structure with the items attribute; if you specify that attribute, <c:forEach> iterates over the data structure. If you don't specify the items attribute, <c:forEach> iterates over the integer values that you specify with the begin and end attributes.If you look at Table 4.5 and Table 4.6, which collectively list all of the LoopTagSupport methods, you will see that the LoopTagSupport class does not implement a setter method for the items attribute. That omission may lead you to believe that custom actions whose tag handlers extend LoopTagSupport are meant to iterate over integer values, but that assumption can get you into trouble. Let's see how.Listing 4.12 lists a simple tag handler, designed to iterate over integer values, that extends LoopTagSupport. Listing 4.12 WEB-INF/classes/tags/SimpleIterationAction.java
Because LoopTagSupport does not provide setter methods for the begin and end properties, the preceding tag handler implements those methods. That tag handler also implements the abstract methods defined by LoopTagSupport listed in Table 4.5 on page 185. The prepare method sets a private count variable to the value specified for the begin attribute, the hasNext method returns true if the count variable is less than or equal to the value specified for the end attribute, and the next method returns an integer value representing the current count and increments that count. Now that our tag handler is implemented, let's see how you can use that tag handler's associated custom action named <core-jstl:simpleIteration>.Listing 4.13 lists a JSP page that iterates with <c:forEach> and <core-jstl:simpleIteration>. Identical attributes are specified for both actions: the begin attribute's value is set to 5 and the end attribute's value is set to 10. Listing 4.13 Using an Iteration Custom Action
The output of the preceding JSP page is shown in Figure 4-9. Figure 4-9. A Broken Custom Iteration Action![]() As you can see from Figure 4-9, the <c:forEach> action properly iterates over the integer values 5 through 10 inclusive; however, the <core-jstl:simpleIteration> action only iterates once. The tag handler for the <core-jstl:simpleIteration> action couldn't be much simpler, but somewhere along the line, something went wrong.The problem with the tag handler listed in Listing 4.12 is that it set the begin and end attributes stored in its superclass (LoopTagSupport). When you set those attributes, LoopTagSupport interprets those values as indexes into an underlying collection. But if you don't explicitly specify a collection, where does the collection come from? The answer is that the values returned from the next method are interpreted by LoopTagSupport as a collection.So then, why did the <core-jstl:simpleIteration> action only iterate once? Here is the answerthe values returned by the next method of the action's tag handler are 5 6 7 8 9 10. As mentioned above, those values are interpreted by LoopTagSupport as a collection, and therefore the begin and end attributes in this case, 5 and 10are interpreted as indexes into that collection. So LoopTagSupport starts iterating over the sixth item (remember that collection indexes are 0-based), which is the value 10. Because that value is the last item in the "collection," the iteration stops after that value.So, how can we fix the tag handler for the <core-jstl:simpleIteration> action so that it properly iterates over integer values? The answer is simple: we implement that tag handler so that it does not set the begin and end attributes stored in its superclass (LoopTagSupport), and therefore LoopTagSupport will not interpret those valuesbecause it won't know about themas indexes into the values returned by the tag handler's next method. The easiest way to do that is to declare begin and end variables in the tag handlerthat declaration hides the variables of the same name in the LoopTagSupport superclass. Listing 4.14 lists the revised tag handler.With the revised tag handler listed above, the <core-jstl:simpleIteration> action will properly iterate over integer values, as you can see from Figure 4-10, which shows the output from the JSP page listed in Listing 4.13 on page 188. Figure 4-10. A Custom Iteration Action That Iterates Over Integer Values Properly![]() Listing 4.14 WEB-INF/classes/tags/SimpleIterationAction.java (revised)
One note of caution before we move on. The preceding discussion is based on the JSTL Reference Implementation. The JSTL specification is rather vague about how the LoopTagSupport class should behave when used as a superclass, as in the preceding example. Because of that vagueness, you may experience different results if you try the preceding example with a JSTL implementation other than the JSTL Reference Implementation.[6] [6] Overall, JSTL is very well designed, but the JSTL Reference Implementation's implementation of the LoopTagSupport class is undoubtedly questionable. Finally, the tag library descriptor (TLD) for the tag library containing the <core-jstl:simpleIteration> action is listed in Listing 4.15. Listing 4.15 WEB-INF/core-jstl.tld
Notice that the preceding TLD must list all of the attributes supported by the <core-jstl:simpleIteration> action, even though setter methods for some of those attributes, namely, the var and varStatus attributes, are implemented by the LoopTagSupport class.Now that we've seen how to properly implement custom actions that iterate over integer values, let's see how to implement a custom action that iterates over a data structure. Custom Actions That Iterate Over Data StructuresThe JSP page shown in Figure 4-11 contains a login form that does not specify an action, so when you activate the Log In button, the JSP page is reloaded. The top picture in Figure 4-11 shows the JSP page just after the form has been filled out, and the bottom picture shows the JSP page after the Log In button has been activated and the page has been reloaded. Figure 4-11. An Iteration Custom Action That Displays Request Parameters![]() The JSP page shown in Figure 4-11 uses a custom action that iterates over request parameters and displays their values. That custom action comes in handy for debugging, especially for forms that post their data, such as the form contained in the JSP page shown in Figure 4-11.The JSP page shown in Figure 4-11 is listed in Listing 4.16. Listing 4.16 Displaying Request Parameters with a Custom Action
The preceding JSP page tests to see whether any of the form's fields have been filled in; if so, the <core-jstl:requestParams> action iterates over the request parameters, which are displayed by the <c:out> action in the body of the <core-jstl:requestParams> action.The tag handler for the <core-jstl:requestParams> action is listed in Listing 4.17. Listing 4.17 WEB-INF/classes/tags/ShowRequest ParametersAction.java
The preceding tag handler implements the three abstract methods defined by the LoopTagSupport class: prepare, hasNext, and next.The prepare method obtains a reference to a map of request parameters and their values, accesses an iterator for that map, and stores it in a private variable. The hasNext method uses the Iterator.hasNext method to determine whether any items are left to iterate over. The next method obtains a reference to the next item in the collection with the Iterator.next method and stores a string with the format key=values in a string buffer, where key is the name of the request parameter and values is a comma-separated string representing that parameter's values. Finally, the next method creates a string from that string buffer and returns it.Listing 4.18 lists the tag library descriptor for the tag library that contains the <core-jstl:requestParams> action.The preceding tag library descriptor declares the <core-jstl:requestParams> action and all of its attributes. Listing 4.18 WEB-INF/core-jstl.tld
|