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
- JSTL 1.0 specification
- JSP 1.2 specification
- JSP Discussion Forum
- The JSTL RI: the Standard Library at Apache Taglibs
Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.
Return to ONJava.com.