Question

Given the following XHTML code (regarding PrimeFaces 5.0 final). New changes to data table filters can be visible here.

<p:dataTable id="dataTable" var="row" value="#{testManagedBean}"
             lazy="true"
             rowKey="#{row.transporterId}"
             widgetVar="dataTableUIWidget">

    <p:column id="id" headerText="Id" sortBy="#{row.transporterId}">
        <h:outputText value="#{row.transporterId}"/>
    </p:column>

    <p:column id="transporter" headerText="Transporter" filterBy="#{row.transporterName}">
        <f:facet name="filter">
            <p:inputText onkeyup="PF('dataTableUIWidget').filter();"/>
        </f:facet>
        <h:outputText value="#{row.transporterName}"/>
    </p:column>
</p:dataTable>

And the following is the faces converter to trim leading and trailing spaces from a string.

@FacesConverter(forClass=String.class)
public final class StringTrimmer implements Converter
{
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        return value != null ? value.trim() : null;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return (String) value;
    }
}

Can this converter be applied globally to all filters of type string (including other UI components which is already applied) as specified in the data table?

<f:facet name="filter">
    <p:inputText onkeyup="PF('dataTableUIWidget').filter();"/>
</f:facet>

Or I have to modify the converter class to have the annotations like,

@ManagedBean
@RequestScoped
public final class StringTrimmer implements Converter
{
    //...
}

and then apply this converter manually to all filters in question like so,

<f:facet name="filter">
    <p:inputText onkeyup="PF('dataTableUIWidget').filter();" converter="#{stringTrimmer}"/>
</f:facet>

Can this converter somehow be applied globally so that we don't need to explicitly specify converter="#{stringTrimmer}" for all string type filters?

The question should be more related to JSF rather than PrimeFaces.

Était-ce utile?

La solution

It's caused because the UIInput#getValue() defaults to Object, not String. As long as you don't explicitly bind the value attribute of an UIInput based component to a backing bean property of a more specific type, like String, no specific converter will be looked up either.

It should work if you change

<p:inputText onkeyup="PF('dataTableUIWidget').filter();"/>

to e.g.

<p:inputText value="#{bean.filter}" onkeyup="PF('dataTableUIWidget').filter();"/>

with a private String filter property (and a getter+setter). But this is indeed clutter if you don't use this property anywhere else in the model.

The alternative is indeed explicitly declaring a converter via the converter attribute. According to @FacesConverter contract, it's not possible to simultaneously declare both a converter ID and a converter for-class on the same converter class like so

@FacesConverter(value="stringTrimmer", forClass=String.class)
public final class StringTrimmer implements Converter {
    // ...
}

Only the converter ID would be registered and a warning will be printed to the server log.

WARNING: @FacesConverter is using both value and forClass, only value will be applied.

But, it is possible to have both @ManagedBean and @FacesConverter on the same class. You should only understand that they don't cooperate with each other and that completely independent instances would be created. But this shouldn't harm if the converter is designed to be stateless (i.e. all state is kept within the method block and the class doesn't have any instance variables/dependencies).

@ManagedBean
@ApplicationScoped
@FacesConverter(forClass=String.class)
public final class StringTrimmer implements Converter {
    // ...
}

This way you can keep having the benefit of forClass and still be able to reference the converter as a managed bean via #{stringTrimmer} on those components where forClass can't apply.

<p:inputText onkeyup="PF('dataTableUIWidget').filter();" converter="#{stringTrimmer}" />

Autres conseils

An alternate to @BalusC answer is to bind the property into a generic map using a helper "getter / setter" wrapper object to not clutter your code with boilerplate bean properties.

PropertyAccessor

public class PropertyAccessor<T> implements Serializable
{
    // bean property path
    private String property;

    // value
    private T value;

    // getter & setter for value
}

Filter map

Save this map in a request or view scoped bean or in your "DAO" wrapping class.

private final Map<String, PropertyAccessor> filterProperties = new HashMap<>();

/**
 * Get default / temporary filter property
 *
 * @param property Name of property
 * @param init Initial default value
 *
 * @return
 */
public <T> PropertyAccessor<T> getFilterProperty(final String property, final T init)
{
    if (!this.filterProperties.containsKey(property))
        this.filterProperties.put(property, new PropertyAccessor<T>(property, init));

    return this.filterProperties.get(property);
}

Example:

A composite component with a generic dao and a statefull filter with converter and optional default value.

<p:column headerText="Barcode" filterBy="#{tblVarProduct.barcode}>
    <f:facet name="filter">
        <p:inputText value="#{cc.attrs.dao.getFilterProperty('barcode', null).value}" 
            converter="BarcodeConverter"
            onchange="PF('#{tblProductWidgetVar}').filter();"/>
    </f:facet>
    <h:outputText value="#{tblVarProduct.barcode}"/>
</p:column>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top