4.5. Paging Tables

Problem

Instead of creating a long page to display a large number of tabular items, you want to display a limited fraction of the data on a page, allowing the user to navigate between the pages.

Solution

The simplest way to perform paging without resorting to a third-party tag library is to leverage the arithmetic capabilities of JSTL EL and the features of JSTL's c:forEach tag. The JSP page (paged_data.jsp) of Example 4-10 presents a complete page that supports paging through a Collection.

Example 4-10. Using JSTL for data paging

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix=
  "bean" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
  <title>Struts Cookbook - Chapter 4 : Paging</title>
</head>
<body>
  <jsp:useBean id="pagedData" class="com.oreilly.strutsckbk.ch04.
  PagedData"/>
  <bean:size id="listSize" name="pagedData" property="data"/>
  <c:set var="pageSize" value="10"/>
  <c:set var="pageBegin" value="${param.pageBegin}"/>  
  <c:set var="pageEnd" value="${pageBegin + pageSize - 1}"/>
  <c:if test="${(pageBegin - pageSize) ge 0}">
    <a href='<c:url value="paged_data.jsp">
               <c:param name="pageBegin" value="${pageBegin - pageSize}"/>
             </c:url>'>
      Prev
    </a>
  </c:if>
  &nbsp;
  <c:if test="${(listSize gt pageSize) and (pageEnd lt listSize)}">
    <a href='<c:url value="paged_data.jsp">
               <c:param name="pageBegin" value="${pageBegin + pageSize}"/>
             </c:url>'>
      Next
    </a>
  </c:if>
  <table border="2">
    <tr>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Term of Office</th>
    </tr>
    <c:forEach var="pres" items="${pagedData.data}" 
             begin="${pageBegin}" end="${pageEnd}">
      <tr>
        <td>
          <c:out value="${pres.firstName}"/>
        </td>
        <td>
          <c:out value="${pres.lastName}"/>
        </td>
        <td>
          <c:out value="${pres.term}"/>
        </td>
      </tr>
    </c:forEach>
  </table>
</body>
</html>

Discussion

An application's usability improves when the user doesn't have to scroll around, vertically or horizontally, to see all the data on a web page. For a business application, this usability, or lack thereof, can make a measurable difference in productivity. This recipe addresses the problem of displaying a collection of data of indeterminate length. This frequently occurs in database-driven applications where the data is rendered in tabular fashion.

The Solution provided can be completely implemented at the presentation level. No requirements are made on the model of the data or on any controller Actions that process the data.

Warning

This solution provides presentation-level paging. It doesn't restrict the data amount initially received from the underlying persistent store.

In the Solution, the data being paged through is hardcoded into the JavaBeans shown in Example 4-11. In a real application, these data would more likely come from a persistence store such as a database or filesystem.

Example 4-11. JavaBean that holds data collection

package com.oreilly.strutsckbk.ch04;

import java.util.ArrayList;
import java.util.List;

public class PagedData {

    private List list;

    public PagedData( ) {
        list = new ArrayList( );
        list.add( new President( "Washington", "George", "1789-97") );
        list.add( new President( "Adams", "John", "1797-1801") );
        list.add( new President( "Jefferson", "Thomas", "1801-09") );
        list.add( new President( "Madison", "James", "1809-17") );
        list.add( new President( "Monroe", "James", "1817-25") );
        list.add( new President( "Jackson", "Andrew", "1829-37") );
        list.add( new President( "Van Buren", "Martin", "1837-41") );
        list.add( new President( "Harrison", "William Henry", "1841") );
        list.add( new President( "Tyler", "John", "1841-45") );
        list.add( new President( "Polk", "James", "1845-49") );
        list.add( new President( "Taylor", "Zachary", "1849-50") );
        list.add( new President( "Fillmore", "Millard", "1850-53") );
        list.add( new President( "Pierce", "Franklin", "1853-57") );
        list.add( new President( "Buchanan", "James", "1857") );
        list.add( new President( "Lincoln", "Abraham", "1861-65") );
        list.add( new President( "Johnson", "Andrew", "1865-69") );
        list.add( new President( "Grant", "Ulysses S.", "1869-77") );
        list.add( new President( "Hayes", "Rutherford B.", "1877-81") );
        list.add( new President( "Garfield", "James", "1881") );
        list.add( new President( "Arthur", "Chester", "1881-85") );
        list.add( new President( "Cleveland", "Grover", "1885-89") );
        list.add( new President( "Harrison", "Benjamin", "1889-93") );
        list.add( new President( "Cleveland", "Grover", "1893-97") );
        list.add( new President( "McKinley", "William", "1897-1901") );
        list.add( new President( "Roosevelt", "Theodore", "1901-09") );
        list.add( new President( "Taft", "William H.", "1909-13") );
        list.add( new President( "Wilson", "Woodrow", "1913-21") );
        list.add( new President( "Jackson", "Andrew", "1829-37") );
        list.add( new President( "Harding", "Warren", "1921-23") );
        list.add( new President( "Coolidge", "Calvin", "1923-29") );
        list.add( new President( "Hoover", "Herbert", "1929-33") );
        list.add( new President( "Roosevelt", "Franklin D.", "1933-45") );
        list.add( new President( "Truman", "Harry", "1945-53") );
        list.add( new President( "Eisenhower", "Dwight", "1953-61") );
        list.add( new President( "Kennedy", "John F.", "1961-63") );
        list.add( new President( "Johnson", "Lyndon", "1963-69") );
        list.add( new President( "Nixon", "Richard", "1969-74") );
        list.add( new President( "Ford", "Gerald", "1974-77") );
        list.add( new President( "Carter", "Jimmy", "1977-81") );
        list.add( new President( "Reagan", "Ronald", "1981-89") );
        list.add( new President( "Bush", "George H.W.", "1989-93") );
        list.add( new President( "Clinton", "William J.", "1993-2001") );
        list.add( new President( "Bush", "George W.", "2001-present") );
    }

    public List getData( ) { 
        return list;
  }
}

The President class encapsulates information about a president, as shown in Example 4-12.

Example 4-12. Value object representing a President

package com.oreilly.strutsckbk.ch04;

public class President {
    public President(String lname, String fname, String term) {
        lastName = lname;
        firstName = fname;
        this.term = term;        
    }

    public String getFirstName( ) {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName( ) {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getTerm( ) {
        return term;
    }
    public void setTerm(String term) {
        this.term = term;
    }

    private String lastName;
    private String firstName;
    private String term;
}

The JSP page (Example 4-10) contains all the logic that handles paging. Though this JSP could have been implemented using only Struts tags, it would have required a substantial amount of scripting to perform the necessary arithmetic. A better approach is to use JSTL. JSTL easily supports arithmetic calculations in expressions; in addition, the JSTL c:forEach tag is designed to allow for specification of beginning and ending indices for the collection being iterated. This ability is a natural fit for the requirements of paging.

Paging requires you to track the current page that the user is on. When the page is initially displayed, you want to display the first n rows, where n is the desired page size. In the Solution, this value—pageSize—is set to 10:

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

The JSP page is designed to accept a request parameter that identifies the beginning index for the current page: pageBegin. The ending index, pageEnd, is calculated by adding the page size to the value of the beginning index. The calculation subtracts 1 from this result because the indices in a c:forEach loop are zero-based.

<c:set var="pageEnd" value="${pageBegin + pageSize - 1}"/>

The JSP page is designed to generate links for the previous and next pages. The link loops back to the current page with the URL query string containing the calculated beginning index. The beginning index for the link to the previous page is calculated as follows:

pageBegin - pageSize

The beginning index for the link to the next page is likewise calculated:

pageBegin + pageSize

Additionally, you don't want the links to display if they aren't valid. In other words, you don't want a link to the previous page when you are displaying the first page. Similarly, there shouldn't be a link to the next page when the last page is displayed. For this requirement, the JSP page needs to know the total size—essentially the number of rows—in the list. This value is determined using the Struts bean:size tag:

<bean:size id="listSize" name="pagedData" property="data"/>

The JSTL c:url tag is used to generate the href attribute for the HTML links:

<a href='<c:url value="paged_data.jsp">
             <c:param name="pageBegin" value="${pageBegin - pageSize}"/>
         </c:url>'>
    Prev
</a>

This results in a link rendered something like the following:

<a href='paged_data.jsp?pageBegin=20'>
  Prev
</a>

Though the Struts html:link tag could have been used, it doesn't add any significant advantage over using the JSTL c:url tag.

Tip

A good practice to use when building JSP pages is to minimize mixing tag libraries when it doesn't add to the overall functionality of the page.

If you examine the Solution, you may notice what appears to be a defect: the ending index is calculated by adding the beginning index to the page size. With the examples, the list being iterated has over 42 elements—e.g., the number of U.S. presidents to date. The first page displays elements 0-9, the second displays 10-19, etc. For the last page, begin will be set to 40 and end will be set to 49. Yet only 42 elements are in the list: end should be set to 41. Fear not, for JSTL is smart enough not to overrun the list. When the c:forEach loop reaches the end of the list, it gracefully discontinues iteration regardless of the value of the end attribute.

Now take a look at the resulting web pages. First, Figure 4-6 shows the rendered web page when the JSP page is initially accessed.

Presidents list (first page)

Figure 4-6. Presidents list (first page)

Figure 4-7 shows the web page after paging forward two pages to display the third page (the 31st through 40th presidents).

Presidents list (third page)

Figure 4-7. Presidents list (third page)

Though the Solution presented satisfies many application requirements, if the size of the data to be displayed is a large result set from a database and the number of rows could be significantly higher—say in the thousands—then you'll need to consider additional alternatives. Look to your underlying persistence mechanism for solutions that manage large data sets at the persistence level.

See Also

The Pager Tag Library is a popular JSP tag library that supports paging. It provides a presentation that can emulate the paging style of search engines such as Google and Alta Vista. Information on this library can be found at http://jsptags.com/tags/navigation/pager/index.jsp.

The display tag library is an open source JSP tag library that is well suited for the display of tabular data. It supports paging in various manners and is discussed in Recipe 4.6.

Get Jakarta Struts Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.