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 == nullreturn false;
    return index >= && index < list.size();
  }

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

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

  public int getRowIndex()
  {
    return index;
  }

  public void setRowIndex(int rowIndex)
  {
    if (rowIndex < -1throw new IllegalArgumentException();
    int old = index;
    index = rowIndex;
    if (list == nullreturn;
    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 = (Listdata;
      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[inew 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 == -|| 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: -->
      <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>

1 comment:

  1. awesome this post is really and this topic is very useful for me,

    more info

    ReplyDelete