Easily disable sorting in PrimeFaces 3's DataTable

PrimeFaces provides a convenient and easy to use sorting facility for its DataTable. Together with Facelets, this facility allows us to create re-usable columns that are natural sortable by default. E.g.:

<ui:composition xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:p="http://primefaces.org/ui"
>
    <p:column headerText="#{headerText}" sortBy="#{value}">
        <h:outputText id="#{id}" value="#{value}" />
    </p:column>    
</ui:composition>

Such a column can be used on a page inside a DataTable as follows:

<my:sortableColumn id="foo" value="#{someBean.someValue}" />

The problem

The above is only a simple example, and real-life usage can be more complex with e.g. default styles and cell editing capabilities added. With such complex tags, it might be desirable to turn off sorting for certain columns programmatically. By default this does not seem to be possible in PrimeFaces. Setting the value attribute in the above example to null or the empty string via an expression prevents sorting, but still renders the sorting icon.

The reason for this is that the PrimeFaces renderer (org.primefaces.component.datatable.DataTableRenderer) checks for the presence of a value expression to determine whether the icon should be rendered, regardless of whether this expression evaluates to something non-empty. So nothing you pass into the value attribute of the above showed tag can make the value expression itself null, nor can any extra attribute accomplish this in the tag's definition.

Custom helper tag

One solution is to build a simple helper tag that takes a parameter and based on that sets the value expression of its parent component to null. This tag can be nested inside a column. Taking it one step further, we can nest the tag inside a table and programmatically disable sorting on all columns. The following shows the implementation of such a tag:

public class ColumnSorterDisabler extends TagHandler {
    
    public ColumnSorterDisabler(TagConfig config) {
        super(config);
    }

    @Override
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
        Boolean disableSorting = getRequiredAttribute("disableSorting").getBoolean(ctx);
        if (disableSorting) {
            if (parent instanceof Column) {
                parent.setValueExpression("sortBy", null);
            } else if (parent instanceof DataTable) {
                for (UIComponent child : parent.getChildren()) {
                    if (child instanceof Column) {
                        child.setValueExpression("sortBy", null);
                    }
                }
            }
        }        
    }    
}

After declaring this in a *-taglib.xml, we can use it as follows inside a Facelets tag:

<ui:composition xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:p="http://primefaces.org/ui"
>
    <my:columnSorterDisabler disableSorting="#{disableSorting}" />

    <p:column headerText="#{headerText}" sortBy="#{value}">
        <h:outputText id="#{id}" value="#{value}" />
    </p:column>    
</ui:composition>

We can now disable sorting on a per-column basis as follows:

<my:sortableColumn id="foo" value="#{someBean.someValue}" disableSorting="true" />

or for an entire table:

<p:dataTable value="#{someBean.someValues}" var="something">
    <my:sortableColumn id="foo" value="#{something.foo}" />
    <my:sortableColumn id="bar" value="#{something.bar}" />
    <my:columnSorterDisabler disableSorting="true" />
</p:dataTable>

One caveat to remember, TagHandlers exist only at 'build-time', so when using expressions to do the disabling the data these point to has to be available during build-time.

The f:attributes tag

Another solution, contributed by my co-worker Bauke Scholtz, is taking advantage of the <f:attribute> tag. This tag does the same thing as an actual tag attribute, but contrary to an actual attribute this one can be conditionally excluded using <c:if>. In that case the implementation of our re-usable column becomes this:

<ui:composition xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:p="http://primefaces.org/ui"
>
    <p:column headerText="#{headerText}">
        <c:if test="#{empty sortable or sortable}">
            <f:attribute name="sortBy" value="#{value}"/>
        </c:if>
        <h:outputText id="#{id}" value="#{value}" />
    </p:column>    
</ui:composition>

Notice how the tag attribute sortBy has been replaced by a child tag. This approach is really convenient since it simply uses JSF's build-in functionality. To disable sorting for all columns at once, the helper tag based approach is still useful though. Note that just like the helper tag, a <f:attribute> tag only exists at build-time, so here too only data that's available during that time can be used.

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

Jakarta EE Survey 2022

What’s new in Jakarta Security 3?