It's a common requirement to display a data set using a table and in most cases there is also a need to let the user sort that data set by columns. The simplest and often encountered case is when the entire data set is returned at once for display and the sorting should be performed by comparing the values displayed in each cell of a given column (i.e. properties of the row object). You'll need to handle the sorting in the backing bean and use <t:commandSortHeader> for each sortable column of each data table of yours like the following:

<t:dataTable var="car"
             value="#{list.cars}"
             sortColumn="#{list.sort}"
             sortAscending="#{list.ascending}"
             preserveDataModel="true"
             preserveSort="true">
        <h:column>
            <f:facet name="header">
                <t:commandSortHeader columnName="type" arrow="true">
                    <h:outputText value="Type"/>
                </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{car.type}" />
        </h:column>
        <h:column>
            <f:facet name="header">
                <t:commandSortHeader columnName="color" arrow="true">
                    <h:outputText value="Color" />
                </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{car.color}" />
        </h:column>
</t:dataTable>

And the code snippet to perform sort (where a SimpleCar instance represents a row object):

public String sort(String column)
{
   Comparator comparator = new Comparator()
   {
       public int compare(Object o1, Object o2)
       {
           SimpleCar c1 = (SimpleCar)o1;
           SimpleCar c2 = (SimpleCar)o2;
           if (column == null)
           {
               return 0;
           }
           if (column.equals("type"))
           {
               return ascending ? c1.getType().compareTo(c2.getType()) : c2.getType().compareTo(c1.getType());
           }
           else if (column.equals("color"))
           {
               return ascending ? c1.getColor().compareTo(c2.getColor()) : c2.getColor().compareTo(c1.getColor());
           }
           else return 0;
      }
   };
   Collections.sort(_cars, comparator);
}

Enable auto sort on all columns

The MyFaces extended data table provides a more straightforward solution for these kinds of cases. You only need to specify sortable="true" on the data table and you'll get the same effect without any custom code. Please note the use of the <t:column> component instead of <h:column>, which allows you to specify the default sorted column. The sort(String column) method is not needed any more in your backing bean:

<t:dataTable var="car"
             value="#{list.cars}"
             sortColumn="#{list.sortColumn}"
             sortAscending="#{list.ascending}"
             preserveDataModel="true"
             preserveSort="true"
             sortable="true">
        <t:column defaultSorted="true">
            <f:facet name="header">
                <h:outputText value="Type"/>                    
            </f:facet>
            <h:outputText value="#{car.type}" />            
        </t:column>
        <t:column>
            <f:facet name="header">
                <h:outputText value="Color" />
            </f:facet>
            <h:outputText value="#{car.color}" />                    
        </t:column>
</t:dataTable>

Enable auto sort by columns

Automatic sorting can be enabled for each column by setting sortable="true" on each <t:column> component as in the following example:

<t:dataTable var="car"
             value="#{list.cars}"
             sortColumn="#{list.sortColumn}"
             sortAscending="#{list.ascending}"
             preserveDataModel="true"
             preserveSort="true">
        <t:column defaultSorted="true" sortable="true">
            <f:facet name="header">
                <h:outputText value="Type"/>                    
            </f:facet>
            <h:outputText value="#{car.type}" />            
        </t:column>
        <t:column>
            <f:facet name="header">
                <h:outputText value="Color" />
            </f:facet>
            <h:outputText value="#{car.color}" />                    
        </t:column>
</t:dataTable>

Note that in this case, the sorting is not enabled on the entire data table, only on the first column; the second column won't be sortable.

How does automatic sorting work

If sortable="true" the data table will do the following:

  1. Wraps the current model with a sortable one and makes it the current model (this wrapper model is provided by MyFaces and the model gets wrapped only if it's not already sortable) 2. Determines which columns are sortable and wraps the current content of the header facet with a command sort header component 3. While iterating over the sortable columns, gets the first output component child of the column and finds the property of the row object that is used to display the cell content from its value attribute

This gets the table into the same state as if you had specified the sort header in each column's header facets yourself. Then the sorting is handled in the sortable model.

How are the sort properties determined

When sorting is enabled on the data table or on each column separately, the sort property is determined from the value attribute of the first output component found in the column's children list. This is done by parsing the expression string of the value binding. For example, in this column:

<t:column>
   <f:facet name="header">
       <h:outputText value="Color" />
   </f:facet>
   <h:outputText value="#{car.color}" />                    
</t:column>

The first output component would then be:

<h:outputText value="#{car.color}" /> 

And the value binding's expression string:

#{car.color}

What we are interested in at this point is to get the value "color" as the property of the row object from this expression string. The string is parsed and the first thing it searches for is the appearance of "car." where "car" is the value of the data table's var attribute.

This is the simplest case but more complex cases are parsed correctly also such as nested properties and collections. For example, the following will work:

#{car.color.type} => sort property is "color.type" or #{car.color[3]} => sort property is "color[3]"

Another thing to note is that if the value binding expression is composed from several expressions using some kind of operators, only the first appearance of a property of the row object will be taken. For example:

#{car.price + car.taxes} => the property taken is "price" and this will be used when sorting

Customizing the sort property

If you need to customize the sort property that is used to sort the values for a column, you can use the propertyName attribute of the <t:commandSortHeader> component, or the sortPropertyName attribute of the <t:column> component because the sort headers are added only if not already present. This is useful when the sorting should be performed by a different property than the displayed one:

<t:column defaultSorted="true" sortable="true">
    <f:facet name="header">
        <t:commandSortHeader columnName="type" arrow="true" propertyName="type">  
            <h:outputText value="Type"/>   
        </t:commandSortHeader>
    </f:facet>
    <h:outputText value="#{car.type}" />            
</t:column>        
<t:column defaultSorted="true" sortable="true" sortPropertyName="type">
    <f:facet name="header">
        <h:outputText value="Type"/>   
    </f:facet>
    <h:outputText value="#{car.type}" />            
</t:column>        

If you don't want to use the propertyName attribute of the <t:commandSortHeader> (Or if you can't... Maybe you want to use a property for sorting not contained in the current row bean) you can also specify custom sort properties the following way:

<t:column sortable="true">
    <f:facet name="header"><h:outputText value="Type"/></f:facet>
    <h:outputText value="#{bean.propertyForSorting}" rendered="#{false}"/>
    <h:outputText value="#{bean.propertyForDisplay}"/>
</t:column>

Enable auto sort with dynamic columns

Instead of listing all columns with a t:column element, you can also have them inserted dynamically with a t:columns element:

<t:dataTable var="car"
             value="#{list.cars}"
             sortColumn="#{list.sortColumn}"
             sortAscending="#{list.ascending}"
             preserveDataModel="true"
             preserveSort="true">
      <t:columns value="#{list.columns}"
                 var="column"
                 sortable="true">
        <f:facet name="header" >
	      <t:commandSortHeader columnName="#{column.field}"
	                           propertyName="#{column.field}"
	                           arrow="true" >
    	      <h:outputText value="#{column.header}" />                    
          </t:commandSortHeader>
        </f:facet>
        <h:outputText value="#{column.value}" />
      </t:columns>
</t:dataTable>

Please note that column.field needs to return the name of the column attribute and not the human readable title of the column. Otherwise sorting won't work.

Static sorting

If all you want to do is sort your row data in the display and you don't want to enable any user-specified sorting, you can specify the following:

<t:dataTable ...
    sortProperty="somePropertyExpression"
    sortable="true"
    sortAscending="true"
    ...
    var="whatever"

This will sort your data on "#{whatever.somePropertyExpression}", i.e., var="car" sortProperty="color.type".

Unfortunately, there appears to be no way to prevent column headers from being links.

Note

  • Automatic sorting works only using properties available on a row object and these must be comparable, i.e. implement the Comparable interface, or else they will be compared as strings
  • The sortable attribute is available starting with version 1.1.3 of Tomahawk
  • No labels