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

advertisement

AddThis Social Bookmark Button

JSTL 1.0: Standardizing JSP, Part 1
Pages: 1, 2, 3

Conditional Processing and Iterations

Let's look at some examples of how you can use the JSTL conditional and iteration actions: <c:if>; the <c:choose>, <c:when>, and <c:otherwise> triple; and <c:forEach>. Along the way, we also use the basic output and variable setting actions: <c:out> and <c:set>.



<c:if> allows you to conditionally include, or process, a piece of the page, depending on runtime information. This sample includes a personal greeting if the user is a repeat visitor, as indicated by the presence of a cookie with the user's name:

<c:if test="${!empty cookie.userName}">
    Welcome back <c:out value="${cookie.userName.value}" />
</c:if> 

The test attribute value is an EL expression that checks if the cookie is present. The empty operator combined with the "not" operator (!) means it evaluates to true if the cookie is not present, causing the element body to be processed. Within the body, the <c:out> action adds the value of the cookie to the response. It's that easy.

Looping through a collection of data is almost as simple. This snippet iterates through a collection of rows from a database with weather information for different cities:

<c:forEach items="${forecasts.rows}" var="city">
   City: <c:out value="${city.name}" />
   Tomorrow's high: <c:out value="${city.high}" />
   Tomorrow's low: <c:out value="${city.low}" />
</c:forEach>

The EL expression for the items value gets the value of the rows property from an object represented by the forecasts variable. As you will learn in future articles, the JSTL database actions represent a query result as an instance of a class named javax.servlet.jsp.jstl.sql.Result. This class can be used as a bean with a number of properties. The rows property contains an array of java.util.SortedMap instances, each one representing a row with column values. The <c:forEach> action processes its body once for each element in the collection specified by the items attribute. Besides arrays, the action works with pretty much any data type that represents a collection, such as instances of java.util.Collection and java.util.Map.

If the var attribute is specified, the current element of the collection is made available to actions in the body as a variable with the specified name. Here it's named city and, since the collection is an array of maps, this variable contain a new map with column values every time the body is processed. The column values are added to the response by the same type of <c:out> actions that you saw in the previous example.

To illustrate the use of the remaining conditional actions, let's extend the iteration example to only process a fixed set of rows for each page request, and add "Previous" and "Next" links back to the same page. The user can then scroll through the database result, looking at a few rows at a time, assuming the Result object is saved in the session scope. Here's how to only process some rows:

<c:set var="noOfRows" value="10" />

<c:forEach items="${forecasts.rows}" var="city"
    begin="${param.first}" end="${param.first + noOfRows - 1}">
    City: <c:out value="${city.name}" />
    Tomorrow's high: <c:out value="${city.high}" />
    Tomorrow's low: <c:out value="${city.low}" />
 </c:forEach>

The <c:set> action sets a variable to the value specified by the value attribute; either a static value, as in this example, or an EL expression. You can also specify the scope for the variable with the scope attribute (page, request, session or application). In this example, I set a variable named noOfRows to 10 in the page scope (the default). This is the number of rows to show for each request.

The <c:forEach> in this example takes the same values for the items and var attributes as before, but I have added two new attributes.

  • The begin attribute takes the 0-based index of the first collection element to process. Here it's set to the value of a request parameter named first. For the first request, this parameter is not available, so the expression evaluates to 0; in other words, the first row.

  • The end attribute specifies the index for the last collection element to process. Here I set it to the value of the first parameter plus noOfRows minus one. For the first request, when the request parameter is missing, this results in 9, so the action loops over indexes 0 through 9.

Next we add the "Previous" and "Next" links:

<c:choose>
  <c:when test="${param.first > 0}">
     <a href="foreach.jsp?first=<c:out value="${param.first - noOfRows}"/>">
                Previous Page</a>
  </c:when>
  <c:otherwise>
     Previous Page
  </c:otherwise>
</c:choose>
<c:choose>
  <c:when test="${param.first + noOfRows < forecasts.rowsCount}">
     <a href="foreach.jsp?first=<c:out value="${param.first + noOfRows}"/>">
                Next Page</a>
  </c:when>
  <c:otherwise>
     Next Page
  </c:otherwise>
</c:choose>

The <c:choose> groups one or more <c:when> actions, each specifying a different Boolean condition. The <c:choose> action tests each condition in order and allows only the first <c:choose> action with a condition that evaluates to true to process its body. The <c:choose> body may also contain a <c:otherwise>. Its body is processed only if none of the <c:when> actions' conditions are true.

In this example, the first <c:when> action tests if the first parameter is greater than 0, i.e. if the page displays a row subset other than the first. If that's true, the <c:when> action's body adds a link back to the same page with the first parameter set to the index of the previous subset. If it's not true, the <c:otherwise> action's body is processed, adding just the text "Previous Page" as a placeholder for the link. The second <c:choose> block provides similar logic for adding the "Next Page" link.

URL Processing

The previous example works fine as long as cookies are used for session tracking. That's not a given; cookies may be turned off, or not supported, in the browser. It's therefore a good idea to enable the container to use URL rewriting as a backup for cookies. URL rewriting, as you may know, means putting the session ID in all URLs used in links and forms in the page. A rewritten URL looks something like this:

myPage.jsp;jspsessionid=ah3bf5e317xmw5

When the user clicks on a link like this, the session ID is sent to the container as part of the URL. The JSTL Core library includes the <c:url> action, which takes care of URL rewriting for you. This is how you can use it to improve the generation of the "Previous Page" link from the previous example:

<c:url var="previous" value="foreach.jsp">
  <c:param name="first" value="${param.first - noOfRows}" />
</c:url>
<a href="<c:out value="${previous}"/>">Previous Page</a>

The <c:url> supports a var attribute, used to specify a variable to hold the encoded URL, and a value attribute for the URL to be encoded. URL query string parameters can be specified using nested <c:param> actions. Special characters in the parameters specified by nested elements are encoded (if needed) and then added to the URL as query string parameters. The final result is passed through the URL rewriting process, adding a session ID if cookie-based session tracking is disabled. In this example, the fully encoded URL is then used as the href attribute value in the HTML link element.

The <c:url> also performs another nice service. As you may be aware, relative URLs in HTML elements must either be relative to the page that contains them or to the root directory of the server (if they start with a slash). The first part of a URL path for a JSP page is called the context path, and it may vary from installation to installation. You should therefore avoid hard-coding the context path in the JSP pages. But sometimes you really want to use a server-relative URL path in HTML elements; for instance when you need to refer to an image file that's located in an /images directory shared by all JSP pages, no matter where in the document structure the page reside. The good news is that if you specify a URL starting with a slash as the <c:url> value, it converts it to a server-relative path. For instance, in an application with the context path /myApp, the <c:url> action converts the path to /myApp/images/logo.gif:

<c:url value="/images/logo.gif" />

There are a few more actions related to URLs in the Core library. The <c:import> action is a more flexible action than the standard <jsp:include> action. You can use it to include content from resources within the same Web application, from another Web application in the same container, or from another server, using protocols like HTTP and FTP. The <c:redirect> action lets you redirect to another resource in the same Web application, in another Web application, or on a different server. Both actions are straightforward to use, so I leave it as an exercise for you to try them out.

Conclusion

In this installment, I have described the JSTL basic building blocks: the set of libraries and the Expression Language. I have also provided examples of how to use most of the actions in the Core library. You can download the JSTL reference implementation and use it with any JSP-1.2-compatible Web container to experiment with these actions. The RI includes a number of examples to help you get started.

In upcoming articles, we will look at the remaining JSTL libraries, including configuration options and how to control their behavior from a controller servlet when using an MVC framework such as Struts. I will also show you how classes defined by the JSTL specification can be used to simplify developing your own custom actions.

Resources

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


Return to ONJava.com.