Not using ADF bindings but wanted to have a sortable table? This post is for you.
I am glad to share a sortable CollectionModel implementation that is so elegantly simple. :)
This model supports in-memory sorting and filtering. The filtering concept based on groovy will be discussed on a subsequent post.
package soadev.ext.trinidad.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.el.ELContext;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.SortCriterion;
public class SortableFilterableModel extends CollectionModel {
private List wrappedData;
private List<Integer> sortedFilteredIndexList;
private Integer baseIndex;
private SortCriterion sortCriterion = null;
public SortableFilterableModel(List wrappedData) {
super();
this.wrappedData = wrappedData;
sortedFilteredIndexList = new ArrayList<Integer>();
for (int i = 0; i < wrappedData.size(); i++) {
sortedFilteredIndexList.add(i);
}
}
public Object getRowKey() {
return isRowAvailable() ? baseIndex : null;
}
public void setRowKey(Object object) {
baseIndex = object == null ? -1 : ((Integer)object);
}
public boolean isRowAvailable() {
return sortedFilteredIndexList.indexOf(baseIndex) != -1;
}
public int getRowCount() {
return sortedFilteredIndexList.size();
}
public Object getRowData() {
return wrappedData.get(baseIndex);
}
public int getRowIndex() {
return sortedFilteredIndexList.indexOf(baseIndex);
}
public void setRowIndex(int i) {
if(i < 0 || i >= sortedFilteredIndexList.size()){
baseIndex = -1;
}else{
baseIndex = sortedFilteredIndexList.get(i);
}
}
public Object getWrappedData() {
return wrappedData;
}
public void setWrappedData(Object object) {
this.wrappedData = (List)object;
}
public List<Integer> getSortedFilteredIndexList() {
return sortedFilteredIndexList;
}
@Override
public boolean isSortable(String property) {
try {
Object data = wrappedData.get(0);
Object propertyValue = evaluateProperty(data, property);
// when the value is null, we don't know if we can sort it.
// by default let's support sorting of null values, and let the user
// turn off sorting if necessary:
return (propertyValue instanceof Comparable) ||
(propertyValue == null);
} catch (RuntimeException e) {
e.printStackTrace();
return false;
}
}
private Object evaluateProperty(Object base, String property) {
ELContext elCtx = FacesContext.getCurrentInstance().getELContext();
//simple property -> resolve value directly
if (!property.contains(".")) {
return elCtx.getELResolver().getValue(elCtx, base, property);
}
int index = property.indexOf('.');
Object newBase =
elCtx.getELResolver().getValue(elCtx, base, property.substring(0,
index));
return evaluateProperty(newBase, property.substring(index + 1));
}
@Override
public List<SortCriterion> getSortCriteria() {
if (sortCriterion == null) {
return Collections.emptyList();
} else {
return Collections.singletonList(sortCriterion);
}
}
@Override
public void setSortCriteria(List<SortCriterion> criteria) {
if ((criteria == null) || (criteria.isEmpty())) {
sortCriterion = null;
// restore unsorted order:
Collections.sort(sortedFilteredIndexList); //returns original order but still same filter
} else {
SortCriterion sc = criteria.get(0);
sortCriterion = sc;
_sort(sortCriterion.getProperty(), sortCriterion.isAscending());
}
}
private void _sort(String property, boolean isAscending) {
if (getRowCount() == 0) {
return;
}
if (sortedFilteredIndexList!= null && !sortedFilteredIndexList.isEmpty()) {
Comparator<Integer> comp = new Comp(property);
if (!isAscending)
comp = new Inverter<Integer>(comp);
Collections.sort(sortedFilteredIndexList, comp);
}
}
private final class Comp implements Comparator<Integer> {
public Comp(String property) {
_prop = property;
}
public int compare(Integer x, Integer y) {
Object instance1 = wrappedData.get(x);
Object value1 = evaluateProperty(instance1, _prop);
Object instance2 = wrappedData.get(y);
Object value2 = evaluateProperty(instance2, _prop);
if (value1 == null)
return (value2 == null) ? 0 : -1;
if (value2 == null)
return 1;
if (value1 instanceof Comparable) {
return ((Comparable<Object>)value1).compareTo(value2);
} else {
// if the object is not a Comparable, then
// the best we can do is string comparison:
return value1.toString().compareTo(value2.toString());
}
}
private final String _prop;
}
private static final class Inverter<T> implements Comparator<T> {
public Inverter(Comparator<T> comp) {
_comp = comp;
}
public int compare(T o1, T o2) {
return _comp.compare(o2, o1);
}
private final Comparator<T> _comp;
}
}
sample page
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<f:view>
<af:document id="d1">
<af:form id="f1">
<af:panelCollection id="pc1">
<f:facet name="menus"/>
<f:facet name="toolbar">
<af:toolbar id="t2">
<af:commandToolbarButton text="Action" id="ctb1"
actionListener="#{viewScope.sortableFilterableForm.action}"/>
</af:toolbar>
</f:facet>
<f:facet name="statusbar"/>
<af:table var="row" rowBandingInterval="0" id="t1"
value="#{viewScope.sortableFilterableForm.model}"
rowSelection="multiple"
binding="#{viewScope.sortableFilterableForm.table}"
selectedRowKeys="#{viewScope.sortableFilterableForm.selection}"
queryListener="#{viewScope.sortableFilterableForm.tableFilter}"
filterModel="#{viewScope.sortableFilterableForm.descriptor}"
filterVisible="true" emptyText="no result found">
<af:column sortable="true" headerText="Job Id" align="start"
id="c2" filterable="true" sortProperty="jobId">
<af:outputText value="#{row.jobId}" id="ot1"/>
</af:column>
<af:column sortable="true" headerText="Job Title" align="start"
id="c4" filterable="true" sortProperty="jobTitle">
<af:outputText value="#{row.jobTitle}" id="ot4"/>
</af:column>
<af:column sortable="true" headerText="Max Salary" align="start"
id="c1" filterable="true" sortProperty="maxSalary">
<af:outputText value="#{row.maxSalary}" id="ot3"/>
</af:column>
<af:column sortable="true" headerText="Min Salary" align="start"
id="c3" filterable="true" sortProperty="minSalary">
<af:outputText value="#{row.minSalary}" id="ot2"/>
</af:column>
<af:column sortable="true" headerText="Job Type" align="start"
id="c5" filterable="true" sortProperty="jobType.color">
<af:outputText value="#{row.jobType.color}" id="ot5"/>
</af:column>
</af:table>
</af:panelCollection>
</af:form>
</af:document>
</f:view>
</jsp:root>
This is somewhat derived from the trinidad
SortableModel implementation but even made simpler.