Previous Section  < Day Day Up >  Next Section

10.1 Displaying a Read-Only Table

With Java, you can represent tabular data in many different ways. Commonly, it's represented as a java.util.List or array with beans or java.util.Map instances representing the columns for each row. If the data is stored in a database, a java.sql.ResultSet or a JSTL javax.servlet.jsp.jstl.sql.Result object typically represents it.

The JSTL <c:forEach> action supports looping over all these data types, with the exception of java.sql.ResultSet, so if all you need is rendering a read-only list or table, you can use JSTL just as you would in a regular JSP page. I actually used this approach in Chapter 8, to verify that the entries added by the action method for the Add button in the report entry form actually were added to the list:

  <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

  <ol>

    <c:forEach items="${reportHandler.currentReportEntries}" var="e">

      <li>Date: ${e.date}, Type: ${e.type}, Amount: ${e.amount}</li>

    </c:forEach>

  </ol>

Here the JSP EL expression for the items attribute gets the List of ReportEntry instances returned by the getCurrentReportEntries( ) method in the ReportHandler class:

package com.mycompany.expense;



import java.util.Collections;

import java.util.Comparator;

import java.util.Date;

import java.util.List;

...



public class ReportHandler {

    ...

    public List getCurrentReportEntries( ) {

        List currentList = currentReport.getEntries( );

        Collections.sort(currentList, new Comparator( ) {

                public int compare(Object o1, Object o2) {

                    Date d1 = ((ReportEntry) o1).getDate( );

                    Date d2 = ((ReportEntry) o2).getDate( );

                    return d1.compareTo(d2);

                }

        });

        return currentList;

    }

    ...

}

This method gets the entries from the current report and sorts them in ascending date order with the help of the java.util.Collections class's sort() method and an anonymous java.util.Comparator class instance that compares the ReportEntry dates. The JSTL <c:forEach> action loops through the elements in the returned List, evaluating its body once for each element, with the current element exposed to the EL expressions in its body through the variable named by the var attribute.

It's important to understand that the <c:forEach> element body can't include any JSF component custom actions, such as <h:outputText>. The reason for this limitation has to do with the fact that the number of times the <c:forEach> action processes its body depends on the runtime value of the items attribute. The first time the page is processed it may loop over the body 10 times, adding new JSF components for the columns in the 10 rows to the view. The next time, it may loop over the body only five times. It would then have to remove the components for the last five rows that were added to the view the last time the page was processed. Part of this particular problem could be solved by letting the component IDs be set by an EL expression, and use the expression to make the IDs unique for the components in each row by appending the row number. It turns out that this opens up another can of worms, so the specification group decided that the best solution was to forbid JSF components actions within the body of a <c:forEach> action (as well as any other custom actions that loop over the element body) and provide a special component type to deal with tabular data instead.

The JSF component for tabular data is called UIData, and the JSF tag library includes an <h:dataTable> action that associates it with a table renderer for generating an HTML table. You can use it to display the current report entries as a read-only table:

<h:dataTable value="#{reportHandler.currentReportEntries}" var="e">

  <h:column>

    <f:facet name="header">

      <h:outputText value="Date" />

    </f:facet>

    <h:outputText value="#{e.date}" />

  </h:column>

  <h:column>

    <f:facet name="header">

      <h:outputText value="Type" />

    </f:facet>

    <h:outputText value="#{e.type}" />

  </h:column>

  <h:column>

    <f:facet name="header">

      <h:outputText value="Amount" />

    </f:facet>

    <h:outputText value="#{e.amount}" />

  </h:column>

</h:dataTable>

It's a lot more to type than the JSTL version, and probably not worth it for a plain read-only table, but it's also much more powerful. As you'll see soon, the <h:dataTable> action allows you to create tables with editable elements, buttons, and links very easily once you've learned the basics.

Let's look at the <h:dataTable> action in more detail. Its value attribute must be a value expression that evaluates to either a List or an array of objects that in turn have properties representing columns; a JDBC java.sql.ResultSet or a JSTL javax.servlet.jsp.jstl.sql.Result representing a database query result; or a JSF data type named javax.faces.model.DataModel. In this example, the currentReportEntries property getter method, shown earlier, returns a List of ReportEntry instances. The action creates a UIData component with the children defined within its body. The UIData component lets its children process the tabular data, once for each element in a List or array or for every row in a database query result, and makes the object that represents the current element or row available to the children through the variable named by the <h:dataTable> action element's var attribute.

Within <h:dataTable> action element's body, use an <h:column> element for each column that you want to display. In the simplest case, the <h:column> element contains just one JSF component action element, suitable for displaying the column's data. For instance, in this example I use an <h:outputText> action element for each column, bound to a property of the ReportEntry bean representing the current row.

10.1.1 Using Facets

Some component types use facets. A facet is different from a regular child component in that it has a special purpose, specified by its name, but any component type can be used as a facet. In a JSP page, a facet is represented by the <f:facet> element. The <h:column> action supports two facets: header and footer. The <h:dataTable> action renders the header facet as a column header and, as you've probably guessed, it renders the footer facet as a column footer. You can specify header and footer facets for the table itself using <f:facet> elements as direct children of the <h:dataTable> element. It renders the table header and footer as rows with a single column spanning the whole table before and after the real table data.

10.1.2 A Word About JSF and Databases

I have already mentioned that the <h:dataTable> action supports values of type java.sql.ResultSet and javax.servlet.jsp.jstl.sql.Result, i.e., the JDBC and JSTL database query result data types. This means you can use the JSTL <sql:query> action to execute an SQL query directly in a JSP page or use JDBC in an action method and expose the ResultSet as a request, session or application scope variable and display the result with <h:dataTable>.

For a very simple application, either of those approaches typically works fine. But when you use JSF, you already have to do quite a bit of Java programming so I recommend that you go the extra mile and create bean classes to represent the database data instead. The ReportEntry, Report, and ReportRegistry classes used in the sample application illustrate one way to represent tabular data as beans. Using beans makes the application much more maintainable than direct database access from JSP or application logic code—if you need to change the database schema to improve performance, or change the SQL statements because you're moving to a different database vendor, the main application code remains the same and only the database abstraction classes need to be modified (in the sample application, all you need to do in a case like this is replace the ReportRegistry implementation).

That's all I'm going to say about JSF and databases, because that's all there is to say about databases that is JSF-specific. To learn more about how to use the JSTL <sql:query> action and JDBC in a web application, I recommend my JavaServer Pages book (O'Reilly), which describes JSTL in detail and includes a brief introduction to JDBC, or a book dedicated to JDBC such as George Reese's Database Programming with JDBC and Java (O'Reilly) or Java Database Best Practices (O'Reilly).

    Previous Section  < Day Day Up >  Next Section