2
votes

I'm building my custom JSF composite component in order to input periodicities. I've got this model:

public class Periodicity implements Serializable {

    private PeriodicityType type;

    private Integer value = 0;

    public PeriodicityExchange() {
        super();
    }

    /**Getter and setters**/

}

Where PeriodicityType is an enum containing DAY, WEEK, MONTH, YEAR.

The input component for this model consists of a p:spinner element to select a numeric value and a h:selectOneMenu to select the periodicity type. The component works well, but now I want to have a java class backing it, so I have modified my facelet file:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:composite="http://xmlns.jcp.org/jsf/composite"
    xmlns:p="http://primefaces.org/ui">

<composite:interface componentType="periodicityInput">
    <composite:attribute name="value" type="model.Periodicity" />
</composite:interface>

<composite:implementation>
    <div class="col-xs-2">
        <p:spinner binding="#{cc.periodicityValue}"
            value="#{cc.attrs.value.value}" min="0" />
    </div>
    <div class="col-xs-3">
        <h:selectOneMenu binding="#{cc.periodicityType}"
            value="#{cc.attrs.value.type}" styleClass="form-control">
            <f:selectItem itemLabel="No periodicity" noSelectionOption="true" />
            <f:selectItem itemLabel="Days" itemValue="DAY" />
            <f:selectItem itemLabel="Weeks" itemValue="WEEK" />
            <f:selectItem itemLabel="Months" itemValue="MONTH" />
            <f:selectItem itemLabel="Years" itemValue="YEAR" />
        </h:selectOneMenu>
    </div>
</composite:implementation>

</html>

And that's my java class (based in this BalusC's tutorial). It's registered as a component using the faces-config.xml file:

public class PeriodicityInput extends UIInput implements NamingContainer {

    private UIInput periodicityValue;

    private UIInput periodicityType;

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        super.encodeBegin(context);
    }

    @Override
    protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
            throws ConverterException {
        try {
            return new PeriodicityExchange(
                    Integer.parseInt(newSubmittedValue.toString().split("-")[0]),
                    newSubmittedValue.toString().split("-")[1]);
        } catch (Exception ex) {
            throw new ConverterException(ex);
        }
    }

    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public UIInput getPeriodicityType() {
        return periodicityType;
    }

    public UIInput getPeriodicityValue() {
        return periodicityValue;
    }

    @Override
    public Object getSubmittedValue() {
        return periodicityValue.getSubmittedValue() + "-" + periodicityType.getSubmittedValue();
    }

    public void setPeriodicityType(UIInput periodicityType) {
        this.periodicityType = periodicityType;
    }

    public void setPeriodicityValue(UIInput periodicityValue) {
        this.periodicityValue = periodicityValue;
    }

}

The issue is that as soon as I use the componentType attribute to bind my component to the java class, the component stops rendering. encodeBegin gets called, but doing some further debugging, I've noticed that when JSF tries to get the renderer for the composite component, it returns null. The method getRenderer(FacesContext) from the UIComponentBase class tries to access context.getRenderKit().getRenderer(getFamily(), rendererType), where the family evaluates to javax.faces.Input and rendererType to javax.faces.Composite.

I'm using Mojarra 2.2.12. What am I missing here?

1

1 Answers

4
votes

where the family evaluates to javax.faces.Input

This is not correct. It's supposed to be javax.faces.NamingContainer, the constant value of UINamingContainer.COMPONENT_FAMILY.

The COMPONENT_FAMILY below is likely messed up during refactoring with static imports:

@Override
public String getFamily() {
    return COMPONENT_FAMILY;
}

The above is actually inherited from UIInput while it has to be UINamingContainer. Better specify it with FQN.

@Override
public String getFamily() {
    return UINamingContainer.COMPONENT_FAMILY;
}