Scrolling Techniques

Table Models

When you use a Java object, array, list, result set, or JSTL result object to represent table data, h:dataTable wraps those objects in a model that extends the javax.faces.model.DataModel class. All of those model classes, listed below, reside in the javax.faces.model package:

  • ArrayDataModel

  • ListDataModel

  • ResultDataModel

  • ResultSetDataModel

  • ScalarDataModel

h:dataTable deals with the models listed above; it never directly accesses the object array, list, etc. you specify with the value attribute. You can, however, access those objects yourself with the DataModel.getWrappedData method. That method comes in handy for adding and removing table rows.

Editing Table Models

It is easy to add and delete table rows with two methods provided by all data models: getWrappedData() and setWrappedData(). Now we see how it works with an application, shown in Figure 5-12, that allows users to delete rows from a table.

Figure 5-12. Deleting table rows


From top to bottom, Figure 5-12 shows the deletion of a single row. When a user activates the "Delete selected names" button, the JSF implementation invokes an action listener method that deletes the selected rows. That method looks like this:

  public String deleteNames() {      if (!getAnyNamesMarkedForDeletion())         return null;      Name[] currentNames = (Name[]) model.getWrappedData();      Name[] newNames = new Name[currentNames.length -         getNumberOfNamesMarkedForDeletion()];      int i=0;      for (Name name : currentNames) {         if (!name.isMarkedForDeletion()) {            newNames[i++] = name;         }      }      model.setWrappedData(newNames);      return null;      }   }

The deleteNames method obtains a reference to the current set of names by calling the model's getWrappedData method. Then it creates a new array whose size is the size of the current array, minus the number of names that have been marked for deletion. Subsequently, the method adds each of the current names to the new array, leaving out names that were marked for deletion. Finally, the method calls the model's setWrappedData method to reset the model with the new array of names.

The JSF implementation invokes the TableData.deleteNames method when the "Delete selected names" button is activated. The deleteNames method obtains a reference to the current array of names by invoking the data models getWrappedData method. The deleteNames method subsequently creates a new array without the names marked for deletion that it pushes to the model with the setWrappedData method.

Although the preceding example does not illustrate adding rows, it does illustrate the principle: Get the current object wrapped by the model with getWrappedData(), modify it (by adding or deleting rows), and then reset the wrapped object with setWrappedData().

Figure 5-13 shows the directory structure for the application. Listing 5-17 through Listing 5-19 list the pertinent files from the application shown in Figure 5-12.

Figure 5-13. The directory structure for the delete example


Note

Calling the model's setWrappedData() to reset the model's data is one way to delete rows. We could have also reset the model itself, like this:

model = new ArrayDataModel(newNames);


Listing 5-17. delete/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core"  prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html"  prefix="h" %>   4.    <f:view>   5.       <head>   6.          <link href="styles.css" rel="stylesheet" type="text/css"/>   7.          <title>   8.             <h:outputText value="#{msgs.windowTitle}"/>   9.          </title>  10.       </head>  11.       <body>  12.          <h:form>  13.             <h:dataTable value="#{tableData.names}" var="name"  14.                style header  15.                columnClasses="last,first">  16.                <h:column rendered="#{tableData.editable}">  17.                   <f:facet name="header">  18.                      <h:outputText value="#{msgs.deleteColumnHeader}"/>  19.                   </f:facet>  20.                   <h:selectBooleanCheckbox value="#{name.markedForDeletion}"  21.                      onchange="submit()"/>  22.                </h:column>  23.                <h:column>  24.                   <f:facet name="header">  25.                      <h:outputText value="#{msgs.lastColumnHeader}"/>  26.                   </f:facet>  27.                   <h:outputText value="#{name.last},"/>  28.                </h:column>  29.                <h:column>  30.                   <f:facet name="header">  31.                      <h:outputText value="#{msgs.firstColumnHeader}"/>  32.                   </f:facet>  33.                   <h:outputText value="#{name.first}"/>  34.                </h:column>  35.             </h:dataTable>  36.             <h:outputText value="#{msgs.editPrompt}"/>  37.             <h:selectBooleanCheckbox onchange="submit()"  38.                value="#{tableData.editable}"/>  39.             <h:commandButton value="#{msgs.deleteButtonText}"  40.                rendered="#{tableData.editable}"  41.                action="#{tableData.deleteNames}"  42.                disabled="#{not tableData.anyNamesMarkedForDeletion}"/>  43.          </h:form>  44.       </body>  45.    </f:view>  46. </html>     

Listing 5-18. delete/src/java/com/corejsf/Name.java

  1. package com.corejsf;   2.   3. public class Name {   4.    private String first;   5.    private String last;   6.    private boolean markedForDeletion = false;   7.   8.    public Name(String first, String last) {   9.       this.first = first;  10.       this.last = last;  11.    }  12.  13.    public void setFirst(String newValue) { first = newValue; }  14.    public String getFirst() { return first; }  15.  16.    public void setLast(String newValue) { last = newValue; }  17.    public String getLast() { return last; }  18.  19.    public boolean isMarkedForDeletion() { return markedForDeletion; }  20.    public void setMarkedForDeletion(boolean newValue) {  21.       markedForDeletion = newValue;  22.    }  23. }     

Listing 5-19. delete/src/java/com/corejsf/TableData.java

  1. package com.corejsf;   2.   3. import javax.faces.model.DataModel;   4. import javax.faces.model.ArrayDataModel;   5.   6. public class TableData {   7.    private boolean editable = false;   8.    private ArrayDataModel model = null;   9.  10.    private static final Name[] names = {  11.       new Name("Anna", "Keeney"),  12.       new Name("John", "Wilson"),  13.       new Name("Mariko", "Randor"),  14.       new Name("William", "Dupont"),  15.    };  16.  17.    public TableData() { model = new ArrayDataModel(names); }  18.  19.    public DataModel getNames() { return model; }  20.  21.    public boolean isEditable() { return editable; }  22.    public void setEditable(boolean newValue) { editable = newValue; }  23.  24.    public String deleteNames() {  25.       if (!getAnyNamesMarkedForDeletion())  26.          return null;  27.  28.       Name[] currentNames = (Name[]) model.getWrappedData();  29.       Name[] newNames = new Name[currentNames.length  30.          - getNumberOfNamesMarkedForDeletion()];  31.  32.       for(int i = 0, j = 0; i < currentNames.length; ++i) {  33.          Name name = (Name) currentNames[i];  34.          if (!name.isMarkedForDeletion()) {  35.             newNames[j++] = name;  36.          }  37.       }  38.       model.setWrappedData(newNames);  39.       return null;  40.    }  41.  42.    public int getNumberOfNamesMarkedForDeletion() {  43.       Name[] currentNames = (Name[]) model.getWrappedData();  44.       int cnt = 0;  45.  46.       for(int i = 0; i < currentNames.length; ++i) {  47.          Name name = (Name) currentNames[i];  48.          if (name.isMarkedForDeletion())  49.             ++cnt;  50.       }  51.       return cnt;  52.    }  53.  54.    public boolean getAnyNamesMarkedForDeletion() {  55.       Name[] currentNames = (Name[]) model.getWrappedData();  56.       for(int i = 0; i < currentNames.length; ++i) {  57.          Name name = (Name) currentNames[i];  58.          if (name.isMarkedForDeletion())  59.             return true;  60.       }  61.       return false;  62.    }  63. }     

We have seen how to perform simple manipulation of a data model. Sometimes, a little more sophistication is required for example, when you sort or filter a model's data.

Sorting and Filtering

To sort or filter tables with h:dataTable, you need to implement a table model that decorates one of the table models listed on page 197. Figure 5-14 shows what it means to decorate a table model.

Figure 5-14. Data model filter


Instances of UIData the component associated with h:dataTable invoke methods on their model. When you decorate that model, your model intercepts those method calls. Your decorator model forwards method calls to the original model, except for the setRowIndex method, which returns a sorted index instead of the original model's index. Next, we see how that works.

Figure 5-15 shows the application discussed in "Editing Table Cells" on page 186, rewritten to support sortable table columns.

Figure 5-15. Sorting table columns


The application shown in Figure 5-15 sorts table columns by decorating a table data model. First, we specify the h:dataTable's value attribute, like this:

  <h:dataTable value="#{tableData.names}" var="name" ...>      ...

The TableData.names method returns a data model:

  public class TableData {      private DataModel filterModel= null;      private static final Name[] names = {         new Name("Anna", "Keeney"),         new Name("John", "Wilson"),         new Name("Mariko", "Randor"),         new Name("William", "Dupont"),      };      public TableData() {         ArrayDataModel model = new ArrayDataModel(names);         filterModel = new SortFilterModel(model);      }      public DataModel getNames() {         return filterModel;      }   }

When the tableData object is created, it creates an ArrayDataModel instance, passing it the array of names. That is the original model. Then the TableData constructor wraps that model in a sorting model. When the getNames method is subsequently called to populate the data table, that method returns the sorting model. The sorting model is implemented like this:

  public class SortFilterModel extends DataModel {      private DataModel model;      private Row[] rows;      ...      public SortFilterModel(DataModel model)  {         this.model = model;         int rowCnt = model.getRowCount();         if (rowCnt != -1) {            rows = new Row[rowCnt];            for (int i=0; i < rowCnt; ++i) {            rows[i] = new Row(i);         }      }      public void setRowIndex(int rowIndex) {         if (rowIndex == -1 || rowIndex >= model.getRowCount()) {         model.setRowIndex(rowIndex);      }      else {         model.setRowIndex(rows[rowIndex].row);         }      }      ...   }     

Notice that we create an array of indices that represent sorted indices. We return a sorted index from the setRowIndex method when the indicated index is in range.

So how does the sorting happen? The SortFilterModel class provides two methods, sortByFirst() and sortByLast():

  public String sortByLast() {      Arrays.sort(rows, byLast);      return null;   }   public String sortByFirst() {      Arrays.sort(rows, byFirst);      return null;   }

The byLast and byFirst variables are comparators. The former compares last names and the latter compares first names. You can see the implementation of the comparators in Listing 5-21 on page 208.

The directory structure for the sorting example is shown in Figure 5-16. Listing 5-20 through Listing 5-26 provide full listings of the application.

Figure 5-16. The directory structure for the sorting example


Note

The JSF 1.2 specification recommends that concrete DataModel classes provide at least two constructors: a no-argument constructor that calls setWrappedData(null) and a constructor that passes wrapped data to setWrappedData(). See Listing 5-21 on page 208 for an example of those constructors.


Listing 5-20. sorting/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core"  prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html"  prefix="h" %>   4.    <f:view>   5.       <head>   6.          <link href="site.css" rel="stylesheet" type="text/css"/>   7.          <title>   8.             <h:outputText value="#{msgs.windowTitle}"/>   9.          </title>  10.       </head>  11.       <body>  12.          <h:form>  13.             <h:dataTable value="#{tableData.names}" var="name"  14.                style header  15.                columnClasses="last,first">  16.                <h:column>  17.                   <f:facet name="header">  18.                      <h:outputText value="#{msgs.deleteColumnHeader}"/>  19.                   </f:facet>  20.                   <h:selectBooleanCheckbox  21.                      value="#{name.markedForDeletion}"  22.                      onchange="submit()"/>  23.                </h:column>  24.  25.                <h:column>  26.                   <f:facet name="header">  27.                      <h:commandLink action="#{tableData.names.sortByLast}">  28.                         <h:outputText value="#{msgs.lastColumnHeader}"/>  29.                      </h:commandLink>  30.                   </f:facet>  31.                   <h:outputText value="#{name.last}, "/>  32.                </h:column>  33.                <h:column>  34.                   <f:facet name="header">  35.                      <h:commandLink action="#{tableData.names.sortByFirst}">  36.                         <h:outputText value="#{msgs.firstColumnHeader}"/>  37.                      </h:commandLink>  38.                   </f:facet>  39.                   <h:outputText value="#{name.first}"/>  40.                </h:column>  41.             </h:dataTable>  42.             <h:commandButton value="#{msgs.deleteButtonText}"  43.                action="#{tableData.deleteNames}"  44.                rendered="#{tableData.anyNamesMarkedForDeletion}"/>  45.          </h:form>  46.       </body>  47.    </f:view>  48. </html>     

Listing 5-21. sorting/src/java/com/corejsf/SortFilterModel.java

  1. package com.corejsf;   2.   3. import java.util.Arrays;   4. import java.util.Comparator;   5. import javax.faces.model.DataModel;   6. import javax.faces.model.DataModelEvent;   7. import javax.faces.model.DataModelListener;   8.   9. public class SortFilterModel extends DataModel {  10.    private DataModel model;  11.    private Row[] rows;  12.  13.    private static Comparator<Row> byLast = new  14.       Comparator<Row>() {  15.          public int compare(Row r1, Row r2) {  16.             Name n1 = (Name) r1.getData();  17.             Name n2 = (Name) r2.getData();  18.             return n1.getLast().compareTo(n2.getLast());  19.          }  20.       };  21.  22.    private static Comparator<Row> byFirst = new  23.       Comparator<Row>() {  24.          public int compare(Row r1, Row r2) {  25.             Name n1 = (Name) r1.getData();  26.             Name n2 = (Name) r2.getData();  27.             return n1.getFirst().compareTo(n2.getFirst());  28.          }  29.       };  30.  31.    private class Row {  32.       private int row;  33.       public Row(int row) {  34.          this.row = row;  35.       }  36.       public Object getData() {  37.          int originalIndex = model.getRowIndex();  38.          model.setRowIndex(row);  39.          Object thisRowData = model.getRowData();  40.          model.setRowIndex(originalIndex);  41.          return thisRowData;  42.       }  43.    }  44.  45.    public SortFilterModel() { // mandated by JSF spec  46.       this((Name[])null);  47.    }  48.    public SortFilterModel(Name[] names) { // recommended by JSF spec  49.       setWrappedData(names);  50.    }  51.    public SortFilterModel(DataModel model) {  52.       this.model = model;  53.       initializeRows();  54.    }  55.  56.    public String sortByLast() {  57.       Arrays.sort(rows, byLast);  58.       return null;  59.    }  60.  61.    public String sortByFirst() {  62.       Arrays.sort(rows, byFirst);  63.       return null;  64.    }  65.  66.    public void setRowIndex(int rowIndex) {  67.       if(rowIndex == -1 || rowIndex >= model.getRowCount()) {  68.          model.setRowIndex(rowIndex);  69.       }  70.       else {  71.          model.setRowIndex(rows[rowIndex].row);  72.       }  73.    }  74.  75.    // The following methods delegate directly to the  76.    // decorated model  77.  78.    public boolean isRowAvailable() {  79.       return model.isRowAvailable();  80.    }  81.    public int getRowCount() {  82.       return model.getRowCount();  83.    }  84.    public Object getRowData() {  85.       return model.getRowData();  86.    }  87.    public int getRowIndex() {  88.       return model.getRowIndex();  89.    }  90.    public Object getWrappedData() {  91.       return model.getWrappedData();  92.    }  93.    public void setWrappedData(Object data) {  94.       model.setWrappedData(data);  95.       initializeRows();  96.    }  97.    public void addDataModelListener(DataModelListener listener) {  98.       model.addDataModelListener(listener);  99.    } 100.    public DataModelListener[] getDataModelListeners() { 101.       return model.getDataModelListeners(); 102.    } 103.    public void removeDataModelListener(DataModelListener listener) { 104.       model.removeDataModelListener(listener); 105.    } 106.    private void initializeRows() { 107.       int rowCnt = model.getRowCount(); 108.       if(rowCnt != -1) { 109.          rows = new Row[rowCnt]; 110.          for(int i=0; i < rowCnt; ++i) { 111.             rows[i] = new Row(i); 112.          } 113.       } 114.    } 115. }     

Listing 5-22. sorting/src/java/com/corejsf/Name.java

  1. package com.corejsf;   2.   3. public class Name {   4.    private String first;   5.    private String last;   6.    private boolean markedForDeletion = false;   7.   8.    public Name(String first, String last) {   9.       this.first = first;  10.       this.last = last;  11.    }  12.  13.    public void setFirst(String newValue) { first = newValue; }  14.    public String getFirst() { return first; }  15.  16.    public void setLast(String newValue) { last = newValue; }  17.    public String getLast() { return last; }  18.  19.    public boolean isMarkedForDeletion() { return markedForDeletion; }  20.    public void setMarkedForDeletion(boolean newValue) {  21.       markedForDeletion = newValue;  22.    }  23. }     

Listing 5-23. sorting/src/java/com/corejsf/TableData.java

  1. package com.corejsf;   2.   3. import javax.faces.model.DataModel;   4. import javax.faces.model.ArrayDataModel;   5.   6. public class TableData {   7.    private DataModel filterModel = null;   8.    private static final Name[] names = {   9.       new Name("Anna", "Keeney"),  10.       new Name("John", "Wilson"),  11.       new Name("Mariko", "Randor"),  12.       new Name("William", "Dupont"),  13.    };  14.  15.    public TableData() {  16.       filterModel = new SortFilterModel(new ArrayDataModel(names));  17.    }  18.    public DataModel getNames() {  19.       return filterModel;  20.    }  21.    public String deleteNames() {  22.       if (!getAnyNamesMarkedForDeletion())  23.          return null;  24.  25.       Name[] currentNames = (Name[]) filterModel.getWrappedData();  26.       Name[] newNames = new Name[currentNames.length  27.          - getNumberOfNamesMarkedForDeletion()];  28.  29.       for(int i = 0, j = 0; i < currentNames.length; ++i) {  30.          Name name = (Name) currentNames[i];  31.          if (!name.isMarkedForDeletion()) {  32.             newNames[j++] = name;  33.          }  34.       }  35.       filterModel.setWrappedData(newNames);  36.       return null;  37.    }  38.  39.    public int getNumberOfNamesMarkedForDeletion() {  40.       Name[] currentNames = (Name[]) filterModel.getWrappedData();  41.       int cnt = 0;  42.  43.       for(int i = 0; i < currentNames.length; ++i) {  44.          Name name = (Name) currentNames[i];  45.          if (name.isMarkedForDeletion())  46.             ++cnt;  47.       }  48.       return cnt;  49.    }  50.  51.    public boolean getAnyNamesMarkedForDeletion() {  52.       Name[] currentNames = (Name[]) filterModel.getWrappedData();  53.       for(int i = 0; i < currentNames.length; ++i) {  54.          Name name = (Name) currentNames[i];  55.          if (name.isMarkedForDeletion())  56.             return true;  57.       }  58.       return false;  59.    }  60. }     

Listing 5-24. sorting/web/WEB-INF/faces-config.xml

  1. <?xml version="1.0"?>   2. <faces-config xmlns="http://java.sun.com/xml/ns/javaee"   3.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   4.    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   5.         http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"   6.    version="1.2">   7.    <application>   8.       <resource-bundle>   9.          <base-name>com.corejsf.messages</base-name>  10.          <var>msgs</var>  11.       </resource-bundle>  12.    </application>  13.  14.    <managed-bean>  15.       <managed-bean-name>tableData</managed-bean-name>  16.       <managed-bean-class>com.corejsf.TableData</managed-bean-class>  17.       <managed-bean-scope>session</managed-bean-scope>  18.    </managed-bean>  19. </faces-config>

Listing 5-25. sorting/web/styles.css

  1. .names {   2.    border: thin solid black;   3. }   4. .namesHeader {   5.    text-align: center;   6.    font-style: italic;   7.    color: Snow;   8.    background: Teal;   9. }  10. .last {  11.    height: 25px;  12.    text-align: center;  13.    background: MediumTurquoise;  14. }  15. .first {  16.    text-align: left;  17.    background: PowderBlue;  18. }  19. .caption {  20.    font-size: 0.9em;  21.    font-style: italic;  22. }     

Listing 5-26. sorting/src/java/com/corejsf/messages.properties

  1. windowTitle=Sorting Java Beans   2. pageTitle=An array of names:   3. firstColumnHeader=First Name   4. lastColumnHeader=Last Name   5. deleteColumnHeader=Delete   6. deleteButtonText=Delete selected names

javax.faces.model.DataModel

  • int getRowCount()

    Returns the total number of rows, if known; otherwise, it returns 1. The ResultSetDataModel always returns 1 from this method.

  • Object getRowData()

    Returns the data associated with the current row.

  • boolean isRowAvailable()

    Returns true if there is valid data at the current row index.

  • int getRowIndex()

    Returns the index of the current row.

  • void setRowIndex(int index)

    Sets the current row index and updates the scoped variable representing the current item in the collection (that variable is specified with the var attribute of h:dataTable).

  • void addDataModelListener(DataModelListener listener)

    Adds a data model listener that is notified when the row index changes.

  • void removeDataModelListener(DataModelListener listener)

    Removes a data model listener.

  • void setWrappedData(Object obj)

    Sets the object that a data model wraps.

  • Object getWrappedData()

    Returns a data model's wrapped data.




Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net