2

I have a composite component (collapsiblePanel). The component uses the "collapsible" bean to provide the toggle function. When I use the same component multiple times on a page, each instance of the component is bound to the same bean instance. How Can I achieve something like a component scoped bean?

collapsibleTemp.xhtml:

<cc:interface>
    <cc:attribute name="model" required="true">
        <cc:attribute name="collapsed" required="true" />
        <cc:attribute name="toggle" required="true"
            method-signature="java.lang.String f()" />
    </cc:attribute>
    <cc:actionSource name="toggle" />
    <cc:facet name="header" />
    <cc:facet name="body" />
</cc:interface>
<cc:implementation>
    <h:panelGroup layout="block" styleClass="collapsiblePanel-header">
        <h:commandButton id="toggle" action="#{cc.attrs.model.toggle}"
            styleClass="collapsiblePanel-img"
            image="#{cc.attrs.model.collapsed ? '/resources/images/plus.png' : '/resources/images/minus.png'}" />
        <cc:renderFacet name="header" />
    </h:panelGroup>
    <h:panelGroup layout="block" rendered="#{!cc.attrs.model.collapsed}">
        <cc:insertChildren />
        <cc:renderFacet name="body"></cc:renderFacet>
    </h:panelGroup>
    <h:outputStylesheet library="css" name="components.css" />
</cc:implementation>

The backing bean:

@ManagedBean
@ViewScoped
public class Collapsible {

    private boolean collapsed = false;

    public boolean isCollapsed() {
        return collapsed;
    }

    public void setCollapsed(boolean collapsed) {
        this.collapsed = collapsed;
    }

    public String toggle() {
        collapsed = !collapsed;
        return null;
    }

}

Using Page

<h:form id="someid">
    <jl:collapsibletemp id="collapsiblePanel1" model="#{collapsible}">
        <f:facet name="header">
            <h3>
                <h:outputText value="Collapsible information" />
            </h3>
        </f:facet>
        <f:facet name="body">
            <h:outputText value="do something....." />
        </f:facet>
        <p />
    </jl:collapsibletemp>

    <jl:collapsibletemp id="collapsiblePanel2" model="#{collapsible}">
        <f:facet name="header">
            <h3>
                <h:outputText value="Collapsible information" />
            </h3>
        </f:facet>
        <f:facet name="body">
            <h:outputText value="do some tabbing" />
        </f:facet>
        <p />
    </jl:collapsibletemp>

    <jl:collapsibletemp id="collapsiblePanel3" model="#{collapsible}">
        <f:facet name="header">
            <h3>
                <h:outputText value="Collapsible information" />
            </h3>
        </f:facet>
        <f:facet name="body">
            <h:outputText value="notice board" />
        </f:facet>
        <p />
    </jl:collapsibletemp>
</h:form>
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Smith
  • 131
  • 3
  • 13

1 Answers1

4

You can use the componentType attribute of the <cc:interface> to define a "backing component".

E.g.

<cc:interface componentType="collapsiblePanel">
    ...
</cc:interface>
<cc:implementation>
    ...
    <h:commandButton action="#{cc.toggle}" ... />
    ...
    <h:panelGroup rendered="#{!cc.collapsed}" ...>
    ...
</cc:implementation>

with just a com.example.components.CollapsiblePanel

@FacesComponent(value="collapsiblePanel") // To be specified in componentType attribute.
public class CollapsiblePanel extends UINamingContainer { // Important! Must extend UINamingContainer.

    private boolean collapsed;

    public void toggle() {
        collapsed = !collapsed;
    }

    public boolean isCollapsed() {
        collapsed;
    }

}

However, when you want to have multiple of those components, then you should declare physically separate instances of them in the view. If this needs to happen dynamically, then you need to use <c:forEach> to generate physically separate instances of them instead of <ui:repeat> with a single component. Otherwise you have to map all collapsed states by the client ID inside a Map<String, Boolean>. See for an example and more background information also Getting same instance of `componentType` in composite component on every use

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • This solution works partially. When I toggle one component, the other component also gets toggled. I have manually declared the components 3 times. When I toggle on any one component, the other 2 also toggle. Thanks for your help. Please let me know what I could do to make these components completely independent of each other. – Smith Jul 07 '11 at 15:22
  • This is exactly what I already predicted and covered in the last paragraph of my answer. Use `` or hold the booleans in a map with client ID as key. – BalusC Jul 07 '11 at 17:36
  • I made the changes as you suggested. But still it does not seem to work. Using Page: @ManagedBean(name="widgets") public class Widgets { public List getWindows() { if(windows == null){ windows = new ArrayList(); windows.add(new Window("one", "one header", "one body")); windows.add(new Window("two", "two header", "two body")); } return windows; Even after adding the collapsed states into a map, they do not behave like independent components. – Smith Jul 08 '11 at 17:43
  • `@FacesComponent(value = "collPanelTwo") class CollPanel extends UICompBase implements NContainer { public void toggle(ActionEvent evt) { collapsed = !collapsed; testMap.put(getClientId(), isCollapsed()); } public boolean isCollapsed() { return testMap.get(getClientId()); }` USING PAGE facets... facets... I have manually added 2 components in the views. They do not behave like independent components. I also tried – Smith Jul 11 '11 at 18:41
  • Thanks BalucS. I had to work around a bit to get it to work. I used the stateHelper to preserve the state. – Smith Jul 21 '11 at 18:53