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) {
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")));
(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")));
(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">
...
More examples are available in JSF 2.2 and Expression Language 3.0 (EL 3.0) - over 50 examples post.
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")));
(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