Ali Cheaito
Ali Cheaito

Reputation: 3856

JSF ValueChangeListener not called for composite with backing component

I'm trying to capture old/new value for some business validation. For that, the ValueChangeListener seemed like a good choice. It worked great on h:selectOneMenu, however it does not get called when used with a home grown Composite Component with a Backing Component. Any idea what I'm doing wrong?

One thing to add is, when removing the componentType attribute from state.xhtml, the valueChangeListener works as expected...

The component:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"  
    xmlns:composite="http://java.sun.com/jsf/composite">

    <composite:interface displayName="state" componentType="com.company.dept.system.ui.address.State" shortDescription="State Information Display/Input Component">
        <composite:attribute name="value" type="java.lang.String" required="true" shortDescription="The value of the component" />      
        <composite:editableValueHolder name="state" />              
    </composite:interface>

    <composite:implementation>
    <div id="#{cc.clientId}">

            <h:selectOneMenu id="state" value="#{cc.attrs.value}">
                <f:selectItem itemLabel="(select)" noSelectionOption="true"/>
                <f:selectItems var="item" itemLabel="#{item.displayValue}" value="#{cc.states}" />
            </h:selectOneMenu>      

    </div>

    </composite:implementation>
</html>

The backing component

@FacesComponent("com.company.dept.system.ui.address.State")
public class State extends UIInput implements NamingContainer {

    private List<com.company.dept.policy.enums.State> states;

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

    /**
     * Prepare the list of states to display
     */
    public List<com.company.dept.policy.enums.State> getStates(){

        if (states != null) {
            return states;
        }

        states = new ArrayList<com.company.dept.policy.enums.State>();              
        for (com.company.dept.policy.enums.State st : com.company.dept.policy.enums.State.values()) {
            if(!st.equals(com.company.dept.policy.enums.State.NWNORWAY) && !st.equals(com.company.dept.policy.enums.State.UNKNOWN) &&  !st.equals(com.company.dept.policy.enums.State.TTTRUST_TERRITORY_AND_GUAM)) {            
                states.add(st);
            }
        }
        Collections.sort(states,new StateNameComparator());

        return states;
    }

}

The value change listener

public class ClientValueChangeListener implements ValueChangeListener {

    @Override
    public void processValueChange(ValueChangeEvent event)
            throws AbortProcessingException {
        System.out.println("*****************************");
        System.out.println("VALUE CHANGE LISTENER. OLD=" + event.getOldValue() + " - NEW=" + event.getNewValue());
        System.out.println("*****************************");
         }
}

consuming page:

<h:form>
    <address:state value="#{testPage.state}">
        <f:valueChangeListener type="com.company.dept.system.ui.clientinformation.ClientValueChangeListener" for="state"/>
    </address:state>
    <h:commandButton id="submitButton" value="Test" action="#{testPage.act}"/>          
</h:form>

Upvotes: 1

Views: 1838

Answers (1)

BalusC
BalusC

Reputation: 1108782

It's because your backing component extends from UIInput. The value change listener is applied to the backing component itself instead of to a child of the composite implementation.

Your concrete functional requirement isn't exactly clear, but based on the information provided so far, you can safely replace extends UIInput implements NamingContainer by extends UINamingContainer (and get rid of getFamily() override).

If you really intend to keep your backing component to extend from UIInput, then you should be delegating the submitted value, local value and value of the backing component all to the child dropdown component, but this makes design technically little to no sense.

Upvotes: 2

Related Questions