yankee
yankee

Reputation: 509

Multiple instances of a composite component in a view?

I read many similar questions, but I cannot figure out how to solve my problem:

I wrote a composite component backed by a view-scoped, self-contained managed bean.
The component consists of an autocomplete textbox with a button that opens a dialog. The user can select an item either by name (autocomplete) or selecting a node in a tree (dialog).
The backing bean implements all the stuff needed (data access, tree-logics etc) and should expose the selected item (as a POJO).

Now I have 2 problems:

  1. Due to the complexity of the tree management, selectedObj property is accessed by a getter and a setter that do some stuff in the bean: they are not simply accessing a class field. Now I'm passing the entire bean as an attribute. How can I just make the bean's selectedObj the "value" attribute of my composite component?

  2. How can I use multiple instance of my component in the same view?

Here is an example of the component:

<cc:interface>
    <cc:attribute name="bean" type="com.yankee.OUTreeBean" required="true"/>
    <cc:attribute name="listener" method-signature="void listener()"/>
</cc:interface>
<cc:implementation>
    <p:dialog id="#{cc.id}_dialog" widgetVar="_dlg" header="Select OU" modal="true" dynamic="true" >
        <p:toolbar>
            <!-- some buttons to refresh, expand, collapse etc. -->
        </p:toolbar>
        <p:tree id="#{cc.id}_tree" value="#{cc.attrs.bean.root}" var="node"
                selectionMode="single"
                selection="#{cc.attrs.bean.selectedNode}">
            <p:ajax event="select" update="@form" listener="#{cc.attrs.listener}" oncomplete="if (!args.validationFailed) _dlg.hide()" />
            <p:treeNode>  
                <h:outputText value="#{node.OU_NAME}" />  
            </p:treeNode>  
        </p:tree>
    </p:dialog>

    <p:autoComplete id="#{cc.id}_inner" value="#{cc.attrs.bean.selectedObj}" completeMethod="#{cc.attrs.bean.completeObj}"  
                    var="obj" itemLabel="#{obj.OU_NAME}" itemValue="#{obj}" 
                    forceSelection="true" 
                    converter="ouConverter"
                    multiple="false" 
                    minQueryLength="2">
        <p:ajax event="itemSelect" listener="#{cc.attrs.listener}" update="@form"/>
    </p:autoComplete>
    <div style="float: right">
        <p:commandButton id="bSearch" icon="ui-icon-search" onclick="_dlg.show()"/>
    </div>
</cc:implementation>

The backing bean of the COMPONENT:

@ManagedBean
@ViewScoped
public class OUTreeBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private List<OU> data;  // Data as plain list
    protected TreeNode root;  // root node of data as a tree
    protected TreeNode selectedNode;

    @PostConstruct
    private void init() throws SQLException {
        refreshData();
    }

    public OU getSelectedObj() {
        if (selectedNode == null) {
            return null;
        }
        return ((OU) selectedNode.getData());

    }
    public void setSelectedObj(OU ou) {
        // Find the right tree node and do whatever needed
    }
    public TreeNode selectedNode getSelectedNode() {
        // Blah blah
    }
    public void setSelectedNode(TreeNode selectedNode) {
        // Blah blah
    }
    public List<OU> completeObj(String namePattern) {
        // Autocomplete handler
    }
    public void refreshData() {
        // Blah blah
    }
    // etc...

}

The using page excerpt:

<ism:selectOUTree id="cbSelectOu" bean="#{myBean.ouFilterBean}" listener="#{myBean.onOUChange}"/>

The backing bean of the PAGE:

@ManagedBean
@ViewScoped
public class MyBean implements Serializable {

    private static final long serialVersionUID = 1L;
    @ManagedProperty("#{oUTreeBean}")
    private OUTreeBean ouFilterBean;

    public void onOUChange() throws SQLException {
        // Blah blah
    }
}

Upvotes: 2

Views: 2937

Answers (1)

pepuch
pepuch

Reputation: 6516

I had the same problem few days ago. Just like you I used ManagedBean as an object that should do the job. After some time I figured out that I should just create FacesComponent. I'm new in JSF so it wasn't that easy to find the solution but it solved all my problems. This is how does it work:

view.xhtml

<h:body>
    <cc:interface componentType="playerComponent">
        <cc:attribute name="playerId" required="true"/>
    </cc:interface>
    <cc:implementation>
        <c:set var="inplaceId" value="inplace-#{cc.attrs.playerId}" />
        <c:set var="outputId" value="output-#{cc.attrs.playerId}" />
        <h:form id="form-#{cc.attrs.playerId}">
            <p:inplace editor="false" widgetVar="#{inplaceId}">
                <h:inputText value="#{cc.player.name}" id="outputId"/>
                <p:commandButton onclick="#{inplaceId}.save()" action="#{cc.save}" update="#{outputId}" value="save" />
                <p:commandButton onclick="#{inplaceId}.cancel()" update="#{outputId}" value="cancel"  />
            </p:inplace>
        </h:form>
    </cc:implementation>
</h:body>

PlayerComponent.java

@FacesComponent("playerComponent")
public class PlayerComponent extends UINamingContainer {

    private Player player;

    private void init() {
        Object idObj = getAttributes().get("playerId");
        if (idObj != null) {
            // create player object
        }
    }

    public void save() {
        // save player object
    }

    public Player getPlayer() {
        if (player == null) {
            init();
        }
        return player
    }
}

Player.java (entity)

public class Player {
    private name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

As I wrote I'm new in JSF and probably player object should be created in different way (using @PostConstruct on in constructor?) but this work.

Upvotes: 1

Related Questions