Tuesday, May 4, 2010

Integrate Spring Webflow, IceFaces and Facelets

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:j2ee="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
 version="2.4">

 <!-- other stuff -->

 <!-- Facelets -->
 <context-param>
  <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
  <param-value>.xhtml</param-value>
 </context-param>

 <context-param>
  <param-name>facelets.DEVELOPMENT</param-name>
  <param-value>true</param-value>
 </context-param>

 <context-param>
  <param-name>com.sun.faces.validateXml</param-name>
  <param-value>true</param-value>
 </context-param>

 <context-param>
  <param-name>facelets.LIBRARIES</param-name>
  <param-value>/WEB-INF/tld/myTaglib.xml</param-value>
 </context-param>

 <servlet>
  <servlet-name>xyz</servlet-name>
  <servlet-class>
   org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet>
  <servlet-name>Faces Servlet</servlet-name>
  <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  <init-param>
   <param-name>debug</param-name>
   <param-value>true</param-value>
  </init-param>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>
    /WEB-INF/xyz-servlet.xml /WEB-INF/xyz-webflow-config.xml
   </param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <!-- Persistent Faces Servlet -->
 <servlet>
  <servlet-name>Persistent Faces Servlet</servlet-name>
  <servlet-class>
   com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <!-- Blocking Servlet -->
 <servlet>
  <servlet-name>Blocking Servlet</servlet-name>
  <servlet-class>
   com.icesoft.faces.webapp.xmlhttp.BlockingServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>xyz</servlet-name>
  <url-pattern>*.htm</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>xyz</servlet-name>
  <url-pattern>*.info</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>/faces/*</url-pattern>
 </servlet-mapping>
 <servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>*.faces</url-pattern>
 </servlet-mapping>

 <!-- Persistent Faces Servlet Mappings -->
 <!--
  the following mapping will load stylesheet and other resources out of
  the icefaces jar by default
 -->
 <servlet-mapping>
  <servlet-name>Persistent Faces Servlet</servlet-name>
  <url-pattern>/xmlhttp/*</url-pattern>
 </servlet-mapping>

 <servlet-mapping>
  <servlet-name>Persistent Faces Servlet</servlet-name>
  <url-pattern>*.iface</url-pattern>
 </servlet-mapping>

 <!-- Blocking Servlet Mapping -->
 <servlet-mapping>
  <servlet-name>Blocking Servlet</servlet-name>
  <url-pattern>/block/*</url-pattern>
 </servlet-mapping>

 <!-- other stuff -->


</web-app>


faces-config.xml

<?xml version="1.0"?>

<!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>

 <application>
  <navigation-handler>
   org.springframework.webflow.executor.jsf.FlowNavigationHandler
  </navigation-handler>
  <variable-resolver>
   org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver
  </variable-resolver>

  <view-handler>com.xyz.MyViewHandler</view-handler>


 </application>

 <lifecycle>
  <phase-listener>
   org.springframework.webflow.executor.jsf.FlowPhaseListener
  </phase-listener>
 </lifecycle>

 <!-- other stuff -->

</faces-config>

The view handler class MyViewHandler.java:
package com.xyz;

import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

import com.icesoft.faces.facelets.D2DFaceletViewHandler;
import com.icesoft.faces.webapp.http.core.ResourceServer;
import com.icesoft.faces.webapp.http.core.ServeCSSResource;

public class MyViewHandler extends ViewHandler
  {
    ViewHandler defaultHandler;
    ViewHandler faceletViewHandler;

    public MyViewHandler(ViewHandler defaultHandler)
    {
      this.defaultHandler = defaultHandler;
      this.faceletViewHandler = new D2DFaceletViewHandler(defaultHandler);
    }

    private ViewHandler getViewHandler(String viewId)
    {
      if (viewId.endsWith(".xhtml"))
      {
        return faceletViewHandler;
      }
      return defaultHandler;
    }

    public UIViewRoot createView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.createView(context, viewId);
    }

    public void renderView(FacesContext context, UIViewRoot viewToRender)
        throws IOException, FacesException
    {
      ViewHandler viewHandler = getViewHandler(viewToRender.getViewId());
      viewHandler.renderView(context, viewToRender);
    }

    public UIViewRoot restoreView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.restoreView(context, viewId);
    }
    
    // other methods
  }
Notes:
  1. MyViewHandler acts as a delegator. By default, it will delegate to the default JSF ViewHandler. But for .xhtml files, it will delegate to D2DFaceletViewHandler, which is an implementation from IceFaces for facelet. This is the glue of Icefaces and Facelets.
  2. The NavigationHandler is the Spring FlowNavigationHandler. This is the glue of Spring webflow and JSF or Icefaces because we are using Icefaces for JSF here.

xyz-webflow-config.xml

This file is linked in the web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:flow="http://www.springframework.org/schema/webflow-config"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
 <flow:executor id="flowExecutor" registry-ref="flowRegistry">
  <flow:execution-attributes>
   <flow:alwaysRedirectOnPause value="false"/>
  </flow:execution-attributes>
 </flow:executor>
    </flow:executor>
 <flow:registry id="flowRegistry"> 
       <flow:location path="/WEB-INF/flows/mytask-flow.xml" />
 </flow:registry>
</beans>

The href link in the jsp page to invoke the webflow

The following link can be put in some menu jsp file. Clicking this will invoke the Spring webflow:

<a href="mytask.iface?_flowId=mytask-flow">My Task</a>

Note that the URL has .iface in it. Because of this, the IceFaces servlet "Persistent Faces Servlet" will be used to process this request as configured in web.xml. And it will invoke Spring webflow because of "_flowId=mytask-flow".

Facelet Template

The real advantage of Facelets is its use of template. You can base all of our concrete xhtml files on the same template so they all have the same look and feel. You can even do sub-template. The following is an example template file (mainTemplate.xhtml):
<f:view  xmlns:f="http://java.sun.com/jsf/core"
         xmlns:h="http://java.sun.com/jsf/html"
         xmlns:ice="http://www.icesoft.com/icefaces/component" 
         xmlns:ui="http://java.sun.com/jsf/facelets">

<html xml:lang="en" lang="en">
<head>
<title>My APP</title>

    <meta http-equiv="Expires" content="-1" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-Control" content="no-cache" />

 <link rel="stylesheet" type="text/css" media="screen" href="/xyz/css/xp.css" />
 <script type="text/javascript" src="/xyz/js/xyzcommon.js"></script>
 <ui:insert name="otherSettings"/>
 
</head>

<body id="page-home">
<div id="page">
    <div id="header" class="foo">  
       <div id="branding">
          <img height='80' src="images/xyz-web-banner.jpg" />
       </div>
    </div>
 <hr />
 
 <div>
     <ui:insert name="content">
            Default Content
     </ui:insert>
 </div>
 
</div>


<div id="footer">
   <p>&copy; Copyright </p>
</div>
</div>

</body>
</html>

</f:view>

Note that it defines two components that that need to be filled out by its sub-tempaltes or the actual concrete xhtml files.
<ui:insert name="otherSettings"/>
and
<ui:insert name="content">
            Default Content
     </ui:insert>
The following is an example sub-template(myTaskTemplate.xhtml):
<ui:composition template="/WEB-INF/jsp/shared/mainTemplate.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
               >
   
<ui:define name="otherSettings">
    <link rel="stylesheet" type="text/css" media="screen" href="/xyz/css/listmenu_o.css" />
    <script type="text/javascript" src="/xyz/js/fsmenu.js"></script>  
    <ui:include src="/WEB-INF/jsp/shared/menuheader.jsp"/>
</ui:define>

</ui:composition>
Note that this sub-template fills out the "otherSettings" part in the main template. But it still leaves the "content" to be filled out. Or in terms of the programming language, it is still an "abstract" class.

The following is an example xhtml file that uses the sub-template:
<ui:composition template="myTaskTemplate.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:ice="http://www.icesoft.com/icefaces/component">
   
<ui:define name="content">
          
<h:form >
  ..................
</h:form>
        
</ui:define>
    
</ui:composition>

Use <ui:include> Tag in Facelet File

Often times there are some common part in an xhtml file that can be shared by multiple xhtml files. The tag <ui:include> can be used to factor out that common part.

The following is an example. Note that this xhtml file(sharedComponent.xhtml) does not use any template because it is an independent component. Also note that it accepts variables:
<ui:component  xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:ice="http://www.icesoft.com/icefaces/component"
                xmlns:c="http://java.sun.com/jstl/core">
               
<ice:panelGroup>
<ice:outputLabel  value="#{myVar.eventType} Name: " />
<ice:outputText value="#{myVar.name} " />
</ice:panelGroup>

</ui:component>

To use this component, one can use the <ui:include> tag and also pass the value to the variabes defined in the component file sharedComponent.xhtml. The following is an example file sample.xhtml
<ui:composition template="myTaskTemplate.xhtml"
                xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:ice="http://www.icesoft.com/icefaces/component"
               >
   
<ui:define name="content">
<ice:outputText styleClass="pageHeading" value="Hello World" />
<ui:include src="sharedComponent.xhtml">
  <ui:param name="myVar" value="#{myForm.aValue}"/>
</ui:include>
<ice:form> 
 .....
</ice:form>
 
</ui:define>
    
</ui:composition>

Friday, April 23, 2010

Sortable JSF DataModel for Spring Webflow

When JSF is integrated with Spring webflow, the DataModel needs to be Serializable. But the standard javax.faces.model.DataModel does not implement the Serializable interface. To get around this problem, we can extend the model. We can also add some artifacts for the column sorting purpose. The following are the two classes for this.

The Serializable DataModel

package mypackage;

import java.io.Serializable;
import java.util.List;

import javax.faces.model.DataModel;
import javax.faces.model.DataModelEvent;
import javax.faces.model.DataModelListener;

/**
 * 
 * SerializableListDataModel is basically the ListDataModel class from
 * jsf-api-1.1, except that it implements the Serializable interface. This
 * interface is needed by ICEfaces when rendering the table.
 */
public class SerializableListDataModel extends DataModel
    implements
      Serializable
{

  public SerializableListDataModel()
  {
    this(null);
  }

  public SerializableListDataModel(List list)
  {
    index = -1;
    setWrappedData(list);
  }

  public boolean isRowAvailable()
  {
    if (list == null) return false;
    return index >= 0 && index < list.size();
  }

  public int getRowCount()
  {
    if (list == null) return -1;
    else
      return list.size();
  }

  public Object getRowData()
  {
    if (list == null) return null;
    if (!isRowAvailable()) throw new IllegalArgumentException();
    else
      return list.get(index);
  }

  public int getRowIndex()
  {
    return index;
  }

  public void setRowIndex(int rowIndex)
  {
    if (rowIndex < -1) throw new IllegalArgumentException();
    int old = index;
    index = rowIndex;
    if (list == null) return;
    DataModelListener listeners[] = getDataModelListeners();
    if (old != index && listeners != null)
    {
      Object rowData = null;
      if (isRowAvailable()) rowData = getRowData();
      DataModelEvent event = new DataModelEvent(this, index, rowData);
      int n = listeners.length;
      for (int i = 0; i < n; i++)
        if (null != listeners[i]) listeners[i].rowSelected(event);

    }
  }

  public Object getWrappedData()
  {
    return list;
  }

  public void setWrappedData(Object data)
  {
    if (data == null)
    {
      list = null;
      setRowIndex(-1);
    }
    else
    {
      list = (List) data;
      index = -1;
      setRowIndex(0);
    }
  }

  private int index;
  private List list;
}

The Sortable DataModel

package mypackage;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Date;

import javax.faces.model.DataModel;
import javax.faces.model.DataModelListener;

import org.springframework.util.StringUtils;

/**
 * 
 * SortableDataModel to make the table columns sortable.
 * 
 */
public class SortableDataModel extends DataModel implements Serializable
{

  private DataModel model;

  private Row[] rows;

  public SortableDataModel()
  {
  }

  public SortableDataModel(DataModel dataModel)
  {
    this.model = dataModel;
    if (model != null)
    {
      initializeRows();
    }
    else
    {
      rows = null;
    }
  }

  protected Row[] getRows()
  {
    return rows;
  }

  private void initializeRows()
  {
    int rowCnt = model.getRowCount();
    if (rowCnt != -1)
    {
      rows = new Row[rowCnt];
      for (int i = 0; i < rowCnt; ++i)
      {
        rows[i] = new Row(i);
      }
    }
  }

  @Override
  public int getRowCount()
  {
    return model.getRowCount();
  }

  @Override
  public Object getRowData()
  {
    return model.getRowData();
  }

  @Override
  public int getRowIndex()
  {
    return model.getRowIndex();
  }

  @Override
  public Object getWrappedData()
  {
    if (model == null)
    {
      return null;
    }
    return model.getWrappedData();
  }

  @Override
  public boolean isRowAvailable()
  {
    return model.isRowAvailable();
  }

  @Override
  public void setWrappedData(Object data)
  {
    model.setWrappedData(data);
  }

  @Override
  public void addDataModelListener(DataModelListener listener)
  {
    model.addDataModelListener(listener);
  }

  @Override
  public DataModelListener[] getDataModelListeners()
  {
    return model.getDataModelListeners();
  }

  @Override
  public void removeDataModelListener(DataModelListener listener)
  {
    model.removeDataModelListener(listener);
  }

  public void setRowIndex(int rowIndex)
  {
    if (rowIndex == -1 || rowIndex >= model.getRowCount())
    {
      model.setRowIndex(rowIndex);
    }
    else
    {
      model.setRowIndex(rows[rowIndex].row);
    }
  }

  protected class Row implements Serializable
  {
    private int row;

    public Row(int row)
    {
      this.row = row;
    }

    public Object getData()
    {
      int originalIndex = model.getRowIndex();
      model.setRowIndex(row);
      Object thisRowData = model.getRowData();
      model.setRowIndex(originalIndex);
      return thisRowData;
    }
  }

  protected abstract static class BaseComparator implements Comparator<Row>
  {
    private boolean ascending;

    public BaseComparator(boolean ascending)
    {
      this.ascending = ascending;
    }

    public abstract int compareRowData(Row o1, Row o2);

    // if the compareRowData returns 0, then use this method to further sort the
    // rows. Usually this will use the column that will definitely return a
    // non-zero value. The subclass comparator can override this method.
    public int compareDefault(Row o1, Row o2)
    {
      return 0;
    }

    protected int compareNullValues(Object obj1, Object obj2)
    {
      if (obj1 != null && obj2 != null)
      {
        throw new IllegalArgumentException("Neither input is null.");
      }
      boolean s1 = (obj1 == null);
      boolean s2 = (obj2 == null);
      int diff = 0;
      if (s1 && !s2)
      {
        diff = 1;
      }
      else if (!s1 && s2)
      {
        diff = -1;
      }
      return diff;
    }

    public int compare(Row o1, Row o2)
    {
      int diff = compareRowData(o1, o2);
      if (diff == 0)
      {
        diff = compareDefault(o1, o2);
      }
      return ascending ? diff : -diff;
    }
  }

  protected abstract static class StringComparator extends BaseComparator
  {
    public StringComparator(boolean ascending)
    {
      super(ascending);
    }

    public abstract String getString(Row row);

    public int compareRowData(Row row1, Row row2)
    {
      String str1 = getString(row1);
      String str2 = getString(row2);
      boolean s1 = StringUtils.hasText(str1);
      boolean s2 = StringUtils.hasText(str2);
      int diff = 0;
      if (s1 && !s2)
      {
        diff = 1;
      }
      else if (!s1 && s2)
      {
        diff = -1;
      }
      else if (!s1 && !s2)
      {
        diff = 0;
      }
      else if (s1 && s2)
      {
        diff = str1.compareTo(str2);
      }
      return diff;
    }
  }

  protected abstract static class IntegerComparator extends BaseComparator
  {
    public IntegerComparator(boolean ascending)
    {
      super(ascending);
    }

    public abstract Integer getInteger(Row row);

    public int compareRowData(Row bean1, Row bean2)
    {
      Integer int1 = getInteger(bean1);
      Integer int2 = getInteger(bean2);
      if (int1 != null && int2 != null)
      {
        return int1.intValue() - int2.intValue();
      }
      return compareNullValues(int1, int2);
    }
  }
  protected abstract static class LongComparator extends BaseComparator
  {
    public LongComparator(boolean ascending)
    {
      super(ascending);
    }

    public abstract Long getLong(Row row);

    public int compareRowData(Row row1, Row row2)
    {
      Long n1 = getLong(row1);
      Long n2 = getLong(row2);
      if (n1 != null && n2 != null)
      {
        Long temp = n1 - n2;
        return temp.intValue();
      }
      return compareNullValues(n1, n2);
    }
  }

  protected abstract static class DateComparator extends BaseComparator
  {
    public DateComparator(boolean ascending)
    {
      super(ascending);
    }

    public abstract Date getDate(Row row);

    public int compareRowData(Row row1, Row row2)
    {
      Date date1 = getDate(row1);
      Date date2 = getDate(row2);
      if (date1 != null && date2 != null)
      {
        return date1.compareTo(date2);
      }
      return compareNullValues(date1, date2);
    }
  }
}

A Sample JSP File Using the Sortable DataModel

The following is a sample jsp file. It is actually an xhtml file because it uses a facelet template. This page also uses IceFaces to display the data page by page. It also uses Spring webflow.

<ui:composition template="myFaceletTemplate.xhtml"
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:ice="http://www.icesoft.com/icefaces/component"
  xmlns:c="http://java.sun.com/jstl/core">

  <ui:define name="content">

    <ice:outputText styleClass="pageHeading"
      value="My Page heading" />
    <br />
    <br />

    <ice:form>
      <ice:panelGroup>
        <!-- Display counts about the table and the currently displayed page -->
        <ice:dataPaginator id="dataScroll_2"
          for="myAppData" rowsCountVar="rowsCount"
          displayedRowsCountVar="displayedRowsCountVar"
          firstRowIndexVar="firstRowIndex" lastRowIndexVar="lastRowIndex"
          pageCountVar="pageCount" pageIndexVar="pageIndex">
          <ice:outputFormat
            value="{0} records found. Displaying reocrds {2} to {3} on page {4} of {5}."
            styleClass="standard">
            <f:param value="#{rowsCount}" />
            <f:param value="#{displayedRowsCountVar}" />
            <f:param value="#{firstRowIndex}" />
            <f:param value="#{lastRowIndex}" />
            <f:param value="#{pageIndex}" />
            <f:param value="#{pageCount}" />
          </ice:outputFormat>
        </ice:dataPaginator>

        <ice:panelGrid columns="2">
          <!-- Layout table columns with column headings and 11 table rows per page -->
          <ice:dataTable id="myAppData"
            var="myAppSelection" styleClass="dataPaginatorTable"
            value="#{myAppAdminForm.myAppSelections}"
            rows="11" columnClasses="firstCol,lastCol,phoneCol,emailCol">
            <ice:rowSelector
              value="#{myAppSelection.selected}" multiple="true" />
            <ice:column>
              <f:facet name="header">
                <ice:outputText id="columnSelection"
                  value="Selected" />
              </f:facet>
              <ice:selectBooleanCheckbox id="selection"
                value="#{myAppSelection.selected}"
                partialSubmit="true" />
            </ice:column>

            <ice:column>
              <f:facet name="header">
                <h:commandLink
                  action="#{myAppAdminForm.myAppSelections.sortByName}">
                  <ice:outputText id="columnName"
                    value="Name" />
                </h:commandLink>
              </f:facet>
              <ice:outputText id="name"
                value="#{myAppSelection.myApp.name}" />
            </ice:column>

            <ice:column>
              <f:facet name="header">
                <h:commandLink
                  action="#{myAppAdminForm.myAppSelections.sortByStartDate}">
                  <ice:outputText id="columnStartDate"
                    value="Start Date" />
                </h:commandLink>
              </f:facet>
              <ice:outputText id="startDate"
                value="#{myAppSelection.myApp.startDate}">
                <f:convertDateTime type="date"
                  dateStyle="medium" />
              </ice:outputText>
            </ice:column>

          </ice:dataTable>

        </ice:panelGrid>
      </ice:panelGroup>

      <!-- Set up the buttons and links for browsing through the table
        No. of pages to fast forward or rewind: 3
        No. of direct links to pages: 4 -->
      <ice:dataPaginator id="dataScroll_1"
        for="myAppData" fastStep="3" pageCountVar="pageCount"
        pageIndexVar="pageIndex" paginator="true" paginatorMaxPages="20"
        styleClass="formBorderHighlight" renderFacetsIfSinglePage="false">
        <f:facet name="first">
          <ice:graphicImage id="firstpage_1"
            url="./xmlhttp/css/xp/css-images/arrow-first.gif"
            style="border:none;" title="first page" />
        </f:facet>
        <f:facet name="last">
          <ice:graphicImage id="lastpage_1"
            url="./xmlhttp/css/xp/css-images/arrow-last.gif"
            style="border:none;" title="last page" />
        </f:facet>
        <f:facet name="previous">
          <ice:graphicImage id="previouspage_1"
            url="./xmlhttp/css/xp/css-images/arrow-previous.gif"
            style="border:none;" title="previous page" />
        </f:facet>
        <f:facet name="next">
          <ice:graphicImage id="nextpage_1"
            url="./xmlhttp/css/xp/css-images/arrow-next.gif"
            style="border:none;" title="next page" />
        </f:facet>
        <f:facet name="fastforward">
          <ice:graphicImage id="fastforward_1"
            url="./xmlhttp/css/xp/css-images/arrow-ff.gif"
            style="border:none;" title="fast forward" />
        </f:facet>
        <f:facet name="fastrewind">
          <ice:graphicImage id="fastrewind_1"
            url="./xmlhttp/css/xp/css-images/arrow-fr.gif"
            style="border:none;" title="fast backward" />
        </f:facet>
      </ice:dataPaginator>

      <table>
        <tr>
          <td>
            <ice:commandButton id="cmdSubmitSelection"
              action="reviewSelections" value="Review Selections" />
            <ice:commandButton action="cancel"
              value="Cancel" immediate="true" />
          </td>
        </tr>
      </table>
    </ice:form>
  </ui:define>
</ui:composition>