2

Walking further down the understanding process of the jsf 2 view scope, I am running into problems again.

There are multiple instances of my composite component's bound object created and setting values does not seem to target the "right" one.

I have the same initial setup as in Auto-instantiate session-scoped bean from view-scoped bean

Now I created a composite component:

<composite:interface componentType="helloWidget">

</composite:interface>

<composite:implementation>
    <h:outputText value="Visible state of this composite component: #{cc.visibleState}"/>
</composite:implementation>

and its java counterpart

@FacesComponent(value = "helloWidget")
public class HelloWidget extends UINamingContainer implements Serializable {

    private static final long serialVersionUID = 2L;
    private boolean visible;

    public void show() {
        System.out.println("Setting visible state to true " + this);
        visible = true;
    }

    public void hide() {
        System.out.println("Setting visible state to false " + this);
        visible = false;
    }

    public String getVisibleState() {
        System.out.println("Getting visible state of " + this + "(" + visible + ")");
        return String.valueOf(visible);
    }
}

I then upgraded my ViewBean.java

private HelloWidget helloWidget;
private boolean visible;

public String getVisibleState() {
    return String.valueOf(visible);
}

public void actionShow(ActionEvent ae) {

    visible = true;
    helloWidget.show();
}

public void actionHide(ActionEvent ae) {

    visible = false;
    helloWidget.hide();
}

public HelloWidget getHelloWidget() {
    return helloWidget;
}

public void setHelloWidget(HelloWidget helloWidget) {
    this.helloWidget = helloWidget;
}

and my hello.xhtml:

<f:view>
    <h:form>
        <h:outputText value="View-scoped bean visible value: #{viewBean.visibleState}"/>
        <br/>
        <mycc:helloWidget binding="#{viewBean.helloWidget}"/>
        <br/>
        <h:commandButton value="Show" actionListener="#{viewBean.actionShow}"/>
        <h:commandButton value="Hide" actionListener="#{viewBean.actionHide}"/>
    </h:form>
</f:view>

When I now hit the show / hide buttons, the value of the "visible" property in the view scoped bean changes as expected. The "visible" value of the HelloWidget property changes too, but when the page is displayed, a different HelloWidget instance is displayed, which has visible set to (default) false.

What am I doing wrong here? Is there a problem with the way I bind the composite component?

Community
  • 1
  • 1
Tom
  • 47
  • 8

1 Answers1

1

The components are instantiated during building/restoring of the view. They are essentially thus request scoped. Any state which you'd like to keep in the view scope needs to be stored in the JSF view state. The normal approach is to let the getters/setters of the properties representing the view scoped state delegate to the UIComponent#getStateHelper().

So, this should do:

@FacesComponent(value = "helloWidget")
public class HelloWidget extends UINamingContainer implements Serializable {

    private static final long serialVersionUID = 2L;

    public void show() {
        setVisible(true);
    }

    public void hide() {
        setVisible(false);
    }

    public Boolean getVisible() {
        return (Boolean) getStateHelper().eval("visible");
    }

    public void setVisible(Boolean visible) {
        getStateHelper().put("visible", visible);
    }
}

You've however another potential problem not related to this issue: using binding on a view scoped bean property causes the view scoped bean itself to be recreated during every request. Use the binding attribtue with extreme care. This problem has the same grounds as already explained in JSTL in JSF2 Facelets... makes sense?.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thank you for your (as usual) invaluable help. I tried this but get a null value returned for my getVisible(), which surely is related to the problem described in your linked answer. Even setting partial state saving to false does not resolve this. So basically, if I want to "control" my custom components from the bean the way I do, I have to fall back to the session scope? – Tom Mar 22 '13 at 14:28