1
votes

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>
1
Which JSF impl/version? What if you explicitly specify <cc:editableValueHolder targets>?BalusC
Using Mojarra 2.1.13 on jBoss EAP 6. I tried setting targets="state" on editableValueHolder but it had no effect.Ali Cheaito
I now see that your backing component extends from UIInput. I'd say, just get rid of it. I posted an answer.BalusC

1 Answers

2
votes

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.