My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

luni, 28 septembrie 2015

Sorting Data Tables (CollectionDataModel, EL 3.0, @FacesDataModel)

In this post, we will discuss about how to implement the sorting feature in JSF data table. Right from the start you should know that our test table will look like in the figure below:
The data are provided in a straightforward manner via two beans. First we have the Player object:

public class Player implements Serializable {

 private String player;
 private byte age;
 private String birthplace;
 ...

 public Player() {
 }

 public Player(int ranking, String player, byte age, String birthplace, String residence, 
               short height, byte weight, String coach, Date born) {
  this.ranking = ranking;
  this.player = player;
  this.age = age;
  ...
 }

 // getters & setters
}

And a simple managed bean, PlayerBean:

@Named
@ViewScoped
public class PlayerBean implements Serializable {
   
 List<Player> data = new ArrayList<>();
 ...

 public PlayerBean() {
  data.add(new Player(2, "NOVAK DJOKOVIC", (byte) 26, "Belgrade, Serbia", "Monte Carlo, Monaco", 
          (short) 188, (byte) 80, "Boris Becker, Marian Vajda", sdf.parse("22.05.1987")));
  ...
 }

 public List<Player> getData() {
  return data;
 }

 public void setData(List<Player> data) {
  this.data = data;
 }
}

Further, in page, we simply use the <h:dataTable>:

...
<h:form>
 <h:dataTable value="#{playerBean.data}" var="t" border="1">
  ...
  <h:column>
   <f:facet name="header">Age</f:facet>
   #{t.age}
  </h:column>
  <h:column>
   <f:facet name="header">Birthplace</f:facet>
   #{t.birthplace}
  </h:column>
  <h:column>
   <f:facet name="header">Residence</f:facet>
   #{t.residence}
  </h:column>
  ...
 </h:dataTable>
</h:form>
...

Simple Sorting
In the previous examples, the data is "arbitrarily" displayed. Sorting the data provides more clarity and accuracy in reading and using the information; for example, you can try to visually localize the number 1 in the ATP ranking, and number 2 and number 3, and so on, but it is much more useful to have the option of sorting the table by the Ranking column. This is a pretty simple task to implement, especially if you are familiar with Java's List, Comparator, and Comparable features. It is beyond our scope to present these features, but you can accomplish most of the sorting tasks by overriding the compare() method, which has a straightforward flow: it compares both of its arguments for order and returns a negative integer, zero, or a positive integer, as the first argument is less than, equal to, or greater than the second. For example, let's see some common sortings:

·         Sort the list of strings, such as player's names. To do this sorting, the code of the compare() method is as follows:

String dir="asc"; //or "dsc" for descending sort
...
Collections.sort(data, new Comparator<Player>() {
 @Override
 public int compare(Player key_1, Player key_2) {
  if (dir.equals("asc")) {
      return key_1.getPlayer().compareTo(key_2.getPlayer());
  } else {
      return key_2.getPlayer().compareTo(key_1.getPlayer());
  }
 }
});
...

·         Sort the list of numbers, such as the player's rankings. To do this sorting, the code of the compare() method is as follows:
...
int dir = 1; //1 for ascending, -1 for descending
...
Collections.sort(data, new Comparator<Player>() {
 @Override
 public int compare(Player key_1, Player key_2) {
  return dir * (key_1.getRanking() - key_2.getRanking());
 }
});
...

·         Sort the list of dates, such as player's birthdays (this works as in the case of strings). To do this sorting, the code of the compare() method is as follows:

...
String dir="asc"; //or "dsc" for descending sort
...
Collections.sort(data, new Comparator<Player>() {
 @Override
 public int compare(Player key_1, Player key_2) {
  if (dir.equals("asc")) {
      return key_1.getPlayer().compareTo(key_2.getPlayer());
  } else {
      return key_2.getPlayer().compareTo(key_1.getPlayer());
  }
 }
});

Note The data argument stands for a List collection type because not all types of collections can take the place of this one. For example, List will work perfectly, while HashSet won't. There are different workarounds to sort collections that are not List collections. You have to ensure that you choose the right collection for your case.

If you know how to write comparators for the selected collection, then everything else is simple. You can encapsulate the comparators in managed beans methods and attach buttons, links, or anything else that calls those methods. For example, you can add these comparators to the PlayerBean backing bean, as shown in the following code:

@Named
@ViewScoped
public class PlayerBean implements Serializable {
   
 List<Player> data = new ArrayList<>();
 ...

 public PlayerBean() {
  data.add(new Player(2, "NOVAK DJOKOVIC", (byte) 26, "Belgrade, Serbia", "Monte Carlo, Monaco", 
          (short) 188, (byte) 80, "Boris Becker, Marian Vajda", sdf.parse("22.05.1987")));
  ...
 }

 public List<Player> getData() {
  return data;
 }

 public void setData(List<Player> data) {
  this.data = data;
 }

 public String sortDataByRanking(final int dir) {

  Collections.sort(data, new Comparator<Player>() {
   @Override
   public int compare(Player key_1, Player key_2) {
    return dir * (key_1.getRanking() - key_2.getRanking());
   }
  });
  return null;
 }

 public String sortDataByName(final String dir) {

  Collections.sort(data, new Comparator<Player>() {
   @Override
   public int compare(Player key_1, Player key_2) {
    if (dir.equals("asc")) {
        return key_1.getPlayer().compareTo(key_2.getPlayer());
    } else {
        return key_2.getPlayer().compareTo(key_1.getPlayer());
    }
   }
  });
  return null;
 }
   
 public String sortDataByDate(final String dir) {
  Collections.sort(data, new Comparator<Player>() {
   @Override
   public int compare(Player key_1, Player key_2) {
    if (dir.equals("asc")) {
        return key_1.getBorn().compareTo(key_2.getBorn());
    } else {
        return key_2.getBorn().compareTo(key_1.getBorn());
    }
   }
  });
  return null;
 }
}

Next, you can easily modify the code of the JSF page to provide access to the sorting feature as follows:
...
<h:form>
 <h:dataTable value="#{playerBean.data}" var="t" border="1">
  <h:column>
   <f:facet name="header">
    <h:commandLink action="#{playerBean.sortDataByRanking(1)}">
     Ranking ASC
    </h:commandLink>
    |
    <h:commandLink action="#{playerBean.sortDataByRanking(-1)}">
     Ranking DSC
    </h:commandLink>
   </f:facet>
   #{t.ranking}
  </h:column>
  <h:column>
   <f:facet name="header">
    <h:commandLink action="#{playerBean.sortDataByName('asc')}">
     Name ASC
    </h:commandLink>
    |
    <h:commandLink action="#{playerBean.sortDataByName('dsc')}">
     Name DSC
    </h:commandLink>
   </f:facet>
   #{t.player}
  </h:column>
  <h:column>
   <f:facet name="header">Age</f:facet>
   #{t.age}
  </h:column>
  <h:column>
   <f:facet name="header">Birthplace</f:facet>
   #{t.birthplace}
  </h:column>
  <h:column>
   <f:facet name="header">Residence</f:facet>
   #{t.residence}
  </h:column>
  <h:column>
   <f:facet name="header">Height (cm)</f:facet>
   #{t.height}
  </h:column>
  <h:column>
   <f:facet name="header">Weight (kg)</f:facet>
   #{t.weight}
  </h:column>
  <h:column>
   <f:facet name="header">Coach</f:facet>
   #{t.coach}
  </h:column>
  <h:column>
   <f:facet name="header">
    <h:commandLink action="#{playerBean.sortDataByDate('asc')}">
     Born ASC
    </h:commandLink>
    |
    <h:commandLink action="#{playerBean.sortDataByDate('dcs')}">
     Born DSC
    </h:commandLink>
   </f:facet>
   <h:outputText value="#{t.born}">               
    <f:convertDateTime pattern="dd.MM.yyyy" />
   </h:outputText>
  </h:column>
 </h:dataTable>
</h:form>
...

The output is shown in the following screenshot:
The complete code of this example is available here.

Improve Simple Sorting
As you can see, each sorting provides two links: one for ascending and one for descending. We can easily glue these links in a switch-link, by using an extra property in our view scoped bean. For example, we can declare a property named sortType, as follows:

private String sortType = "asc";

Add a simple condition to make it act as a switch between ascending and descending sort as shown in the following code:

public String sortDataByRanking() {

 Collections.sort(data, new Comparator<Player>() {
  @Override
  public int compare(Player key_1, Player key_2) {
   if (sortType.equals("asc")) {
       return key_1.getRanking() - key_2.getRanking();
   } else {
       return (-1) * (key_1.getRanking() - key_2.getRanking());
   }
  }
 });
 sortType = (sortType.equals("asc")) ? "dsc" : "asc";
 return null;
}

Now, the JSF page should be adjusted as below:
...
<h:form>
 <h:dataTable value="#{playerBean.data}" var="t" border="1">
  <h:column>
   <f:facet name="header">
    <h:commandLink action="#{playerBean.sortDataByRanking()}">
     Ranking
    </h:commandLink>                       
   </f:facet>
   #{t.ranking}
  </h:column>
  <h:column>
   <f:facet name="header">Name</f:facet>
   #{t.player}
  </h:column>
  ...
 </h:dataTable>
</h:form>

Since we did this only for the Ranking column, we will obtain something like in figure below:
The complete code of this example is available here.

Sorting via EL 3.0
Starting with EL 3.0 and lambda  expressions support, we can easily achieve sorting tasks. For example, we can "nest" a lambda expression directly in the value attribute of the data table, and obtain the table data displayed sorted ascending by players names:

<h:dataTable value="#{(playerBean.data.stream().sorted((x,y)->x.player.compareTo(y.player))).toList()}" var="t" border="1">
...

Sorting and DataModel  (JSF 2.2 CollectionDataModel)
A more complex sorting example involves a decorator class that extends the javax.faces.model.DataModel class. JSF uses a DataModel class even if we are not aware of it, because each collection (List, array, HashMap and so on) is wrapped by JSF in a DataModel class (or, in a subclass, as ArrayDataModel, CollectionDataModel, ListDataModel, ResultDataModel, ResultSetDataModel, or ScalarDataModel). JSF will call the table DataModel class's methods when it renders/decodes table data. In the following screenshot, you can see all directly known subclasses of the DataModel class:
Sometimes you need to be aware of the DataModel class because you need to alter its default behavior. (it is recommended that you take a quick look at the official documentation of this class's section at https://javaserverfaces.java.net/nonav/docs/2.2/javadocs/ to obtain a better understanding) The most common cases involve the rendering row numbers, sorting, and altering the row count of a table. When you do this, you will expose the DataModel class instead of the underlying collection.

For example, let's suppose that we need to use a collection, such as HashSet. This collection doesn't guarantee that the iteration order will remain constant over time, which can be a problem if we want to sort it. Of course, there are a few workarounds, such as converting it to List or using TreeSet instead, but we can alter the DataModel class that wraps the HashSet collection, which is the new JSF 2.2 class, CollectionDataModel.

We can accomplish this in a few steps, which are listed as follows:

1. Extend the CollectionDataModel class for overriding the default behavior of its methods, as shown in the following code:

public class SortDataModel<T> extends CollectionDataModel<T> {
...

2. Provide a constructor and use it for passing the original model (in this case, CollectionDataModel). Besides the original model, we need an array of integers representing the indexes of rows (For example, rows[0]=0, rows[1]=1, ... rows[n]= model.getRowCount()). Sorting the row indexes will actually sort the HashSet collection, as shown in the following code:

...
CollectionDataModel<T> model;
private Integer[] rows;
public SortDataModel(CollectionDataModel<T> model) {
 this.model = model;
 initRows();
}

private void initRows() {
 int rowCount = model.getRowCount();
 if (rowCount != -1) {
     this.rows = new Integer[rowCount];
     for (int i = 0; i < rowCount; ++i) {
          rows[i] = i;
     }
 }
}
...

3. Next, we need to override the setRowIndex() method to replace the default row index, as shown in the following code:

@Override
public void setRowIndex(int rowIndex) {
 if ((0 <= rowIndex) && (rowIndex < rows.length)) {
     model.setRowIndex(rows[rowIndex]);
 } else {
     model.setRowIndex(rowIndex);
 }
}

4. Finally, provide a comparator as follows:

public void sortThis(final Comparator<T> comparator) {
 Comparator<Integer> rowc = new Comparator<Integer>() {
  @Override
  public int compare(Integer key_1, Integer key_2) {
   T key_1_data = getData(key_1);
   T key_2_data = getData(key_2);
   return comparator.compare(key_1_data, key_2_data);
  }
 };
 Arrays.sort(rows, rowc);
}

private T getData(int row) {
 int baseRowIndex = model.getRowIndex();
 model.setRowIndex(row);
 T newRowData = model.getRowData();
 model.setRowIndex(baseRowIndex);
 return newRowData;
}

5. Now, our custom CollectionDataModel class with sorting capabilities is ready. We can test it by declaring and populating HashSet, wrapping it in the original CollectionDataModel class, and passing it to the custom SortDataModel class, as shown in the following code (PlayerBean):

private HashSet<Player> dataHashSet = new HashSet<>();
private SortDataModel<Player> sortDataModel;
...
public PlayerBean() {
 dataHashSet.add(new Players(2, "NOVAK DJOKOVIC", (byte) 26, "Belgrade, Serbia", "Monte Carlo, Monaco",
                 (short) 188, (byte) 80, "Boris Becker, Marian Vajda", sdf.parse("22.05.1987")));
 ...
 sortDataModel = new SortDataModel<>(new CollectionDataModel<>(dataHashSet));
}
...

public SortDataModel<Player> getSortDataModel() {
 return sortDataModel;
}
...

6. Since we are the caller, we need to provide a comparator:

public String sortDataByRanking() {

 sortDataModel.sortThis(new Comparator<Player>() {
  @Override
  public int compare(Player key_1, Player key_2) {
   if (sortType.equals("asc")) {
       return key_1.getRanking() - key_2.getRanking();
   } else {
       return (-1) * (key_1.getRanking() - key_2.getRanking());
   }
  }
 });
 sortType = (sortType.equals("asc")) ? "dsc" : "asc";
 return null;
}

7. Adjust the JSF page to use the SortDataModel:

<h:dataTable value="#{playerBean.sortDataModel}" var="t" border="1">
...

The complete code of this example is available here.

Keep an eye on JSF 2.3 feature
With @FacesDataModel custom DataModel wrappers can be registered, but those wrappers cannot (yet) override any of the build-in types.

You may want to read

Out of the box data tables
If you want to take advantages of the sorting capability out of the box (among many other facilities), then you can try some data tables from here:

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors