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

advertisement

AddThis Social Bookmark Button

JSTL 1.0: What JSP Applications Need, Part 3
Pages: 1, 2

Developing JSTL-Style Iteration Custom Actions

Developing a custom iteration action can also be simplified by extending a JSTL base class, and custom actions nested within a JSTL <c:forEach> action body have easy access to iteration status information through a JSTL interface.



Let's look at a custom iteration action first. The JSTL base class you can extend is javax.servlet.jsp.jstl.core.LoopTagSupport. All you really need to implement in the subclass are three methods: prepare(), hasNext(), and next(). This gives you iteration plus support for the same var and varStatus attributes as the JSTL <c:forEach> action. If you want to support the begin, end, and step attributes, the base class provides protected fields and validation methods, but you have to implement the setter methods yourself (since not all subclasses need them, and the details differ, depending on if EL expressions are allowed or not).

To see how you can extend the JSTL base class for your own iteration action, let's develop a custom action that iterates through all days in the current month. The tag handler looks like this:

package com.ora.jstl;

import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.jstl.core.LoopTagSupport;

public class ForEachDayTag extends LoopTagSupport {
   private Calendar calendar;
   private int previousMonth;

   public void prepare() {
      calendar = new GregorianCalendar();
      calendar.set(Calendar.DAY_OF_MONTH, 1);
      // Set to last day in previous month, since next() increments it
      calendar.add(Calendar.DAY_OF_MONTH, -1);
      previousMonth = calendar.get(Calendar.MONTH);
   }

   public boolean hasNext() {
      int currentMonth = calendar.get(Calendar.MONTH);
      int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
      int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
      return currentMonth == previousMonth || currentDay < lastDay;
   }

   public Object next() {
      calendar.set(Calendar.DAY_OF_MONTH, 
          calendar.get(Calendar.DAY_OF_MONTH) + 1);
      return calendar;
   }
}

The base class calls prepare() once, followed by a sequence of calls to hasNext() and next() until hasNext() returns false. The subclass code is pretty straight forward. The prepare() method creates a GregorianCalendar instance and sets it to the last day of the previous month. The hasNext() method returns true if the day currently represented by the calendar is either a day in the previous month (i.e., before the first iteration) or a day other than the last day of the current month. The next() method, finally, moves the calendar to the next day and returns the adjusted calendar.

Here's an example of how you can use this custom iterator to generate an HTML table with a cell for each day in the current month:

<table>
   <xmp:forEachDay var="curr">
      <tr>
         <td>
            <fmt:formatDate value="${curr.time}" 
               pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

It would be fairly easy to extend this custom action to support the begin, end, and step attributes, and maybe an attribute for setting the month to iterate over. I leave that as an exercise for you to try out on your own.

Using JSTL Iteration Status Info

What if you want to do things only for certain items in the body of an iteration action? The JSTL <c:forEach> action and custom actions extending the LoopTagSupport base class expose information about the current item through a variable named by the varStatus attribute. This variable is an instance of a bean with properties like first, last, index, and more (see the JSTL specification for details). For instance, you can use it like this to get alternating colors for the rows in a table:

<table>
   <xmp:forEachDay var="curr" varStatus="stat">
      <c:set var="bg" value="white" />
      <c:if test="${stat.index % 2 == 0}">
         <c:set var="bg" value="blue" />
      </c:if>
      <tr bgcolor="<c:out value="${bg}" />">
         <td>
           <fmt:formatDate value="${curr.time}" 
              pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

Sometimes it's impossible to use an EL expression testing the status bean properties (or the current item itself) to figure out if special processing is needed or not. With the calendar iterator, for instance, you can't use an EL expression to find out what day in the week the current item represents. This is where a custom action specifically intended for use within an iterator action body can come in handy.

A custom action can use the knowledge that a JSTL iterator action implements the javax.servlet.jsp.jstl.core.LoopTag interface to get access to the current item and the iteration status iformation. Here's the tag handler code for a custom action that processes its body only if the current item represents a Sunday:

package com.ora.jstl;

import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.jstl.core.ConditionalTagSupport;
import javax.servlet.jsp.jstl.core.LoopTag;

public class IfSundayTag extends ConditionalTagSupport {
   public boolean condition() throws JspTagException {
      LoopTag parent = 
          (LoopTag) findAncestorWithClass(this, LoopTag.class);
      if (parent == null) {
         throw new JspTagException("ifSunday must be used in loop");
      }
      Calendar current = (Calendar) parent.getCurrent();
      return current.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
   }
}

The LoopTag interface declares two methods: getCurrent() returns the current iteration item as an Object and getLoopStatus() returns an instance of LoopStatus (the same type as for the object exposed as the varStatus variable). The interface is implemented by the LoopTagSupport base class, so all tag handlers that extend this class get the correct behavior for free.

In this example tag handler, the parent that implements the LoopTag interface (our ForEachDayTag tag handler) is located using the findAncestorWithClass() method and the current item is retrieved by calling the parent's getCurrent() method. If the current item represents a Sunday, the condition() method returns true. With this custom action, it's easy to do whatever you want with Sundays:

<table>
   <xmp:forEachDay var="curr">
      <c:set var="bg" value="white" />
      <xmp:ifSunday>
         <c:set var="bg" value="red" />
      </xmp:ifSunday>
      <tr bgcolor="<c:out value="${bg}" />">
         <td>
            <fmt:formatDate value="${curr.time}" 
               pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

A custom action that needs to do something only for the first or last iteration, or perhaps only for every second or third iteration, can use the getLoopStatus() method to get the information it needs.

Using JSTL Classes to Produce Localized Text

There's one more JSTL class that you may find useful when you develop custom actions: the javax.servlet.jsp.jstl.fmt.LocaleSupport class. This class provides methods for getting localized messages from a ResourceBundle, using the same algorithms as the JSTL i18n actions for determining the appropriate locale (as I described in part 2 of this article series).

The class provides the following methods:

public static String getLocalizedMessage(PageContext pc, 
   String key);
public static String getLocalizedMessage(PageContext pc, String key, 
   String basename);
public static String getLocalizedMessage(PageContext pc, String key,
   Object[] args);
public static String getLocalizedMessage(PageContext pc, String key, 
   Object[] args, String basename);

The first two methods get a simple localized message for the specified key. The second method uses the specified basename to locate the correct ResourceBundle, while the first one uses the bundle selected for the current localization context. The second pair of methods are for parameterized messages, using the args parameter to set the message parameters.

Conclusion

If you've read all parts of this article series, you have a glimpse of what JSTL 1.0 has to offer, whether you're a page author or a programmer. I've covered all features except the JSTL XML processing tag library; it works pretty much the same as the other libraries and if you know XML and XPath, I'm sure you can figure out how to use it on your own. If you don't know XML and XPath, that's where you need to start, and I'm afraid that's out of scope for this article.

While you can get an idea about the possibilities from reading an article, the only way to really learn how to use a technology is to do just that: use it! The Resouce section gives you some pointers to where you can find out more about JSTL and where to ask questions. I hope you'll find JSTL both fun and useful.

Resources

Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.


Return to ONJava.com.