4

This question is related with my previous one about initializing composite components. Providing I've got a homegrown composite component and a @ViewScoped managed bean, I want certain properties of the managed bean to be reflected in the component. The transition of the property value to the component is done by a composite attribute.

That's the code I've got right now, consisting in a @ManagedBean which defines a random row number to be rendered at the component. The component generates a h:dataTable with a h:commandButton in each row, pressing that button just calls a method in the component backing code and performs a postback to the same view.

Managed bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    private Integer rowNum;

    public Integer getRowNum() {
        return rowNum;
    }

    /**
     * Initializes the rowNum property with a value between 1 and 9
     */
    public void init() {
        if (!FacesContext.getCurrentInstance().isPostback()) {
            rowNum = new Random().nextInt(10) + 1;
            System.out.println("Bean initialized");
        }
    }

    public void setRowNum(Integer rowNum) {
        this.rowNum = rowNum;
    }

}

index.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
    <f:event type="preRenderView" listener="#{bean.init}" />
    <h:form>
        <comp:myTable itemNumber="#{bean.rowNum}" />
    </h:form>
</body>
</html>

MyTable.java:

@FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {

    private List<String> values = new ArrayList<String>();

    public void action() {
        System.out.println("Called");
    }

    public List<String> getValues() {
        return values;
    }

    /**
     * Initializes the 'values' List based in 'itemNumber' attribute
     */
    public void init() {
        System.out.println("Component initialized");
        Integer num = (Integer) getAttributes().get("itemNumber");
        for (int i = 0; i < num; i++) {
            values.add("item" + i);
        }
    }

}

myTable.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">

<h:body>
    <composite:interface componentType="components.myTable">
        <composite:attribute name="itemNumber" 
            type="java.lang.Integer" required="true" />
    </composite:interface>

    <composite:implementation>
        <f:event type="preRenderView" listener="#{cc.init}" />
        <h:dataTable value="#{cc.values}" var="value">
            <h:column headerText="column">
                #{value}
                <h:commandButton value="Action" action="#{cc.action}" />
            </h:column>
        </h:dataTable>
    </composite:implementation>
</h:body>
</html>

Given that components themselves are stateless, the row number to get rendered must be determined by the Bean instance in order not to loose its value when a postback happens.

Either the @ManagedBean and the component are initialized by an init() method which gets called when preRenderView event happens. The @ManagedBean checks it's not actually a postback request, considering it has to maintain the view state. The component however, being stateless, doesn't perform that checking because it loses its value in each request and have to be initialized in all of them.

The issue is clicking any of the h:commandButton makes an empty List to be rendered. It seems like it's being read before the init() method gets called. That doesn't happen however when a GET request is performed.

A way to avoid that issue is doing a lazy initialization at getValues() method, invoking init() method there instead of doing it from the view with a preRenderView event, despite I consider it not being the most elegant way to go with.

Using postAddToView instead of preRenderView event to initialize the component neither work, as it is invoked before the @ManagedBean gets initialized.


Tested in Mojarra 2.1.27 - 2.2.5 + Tomcat 7

Community
  • 1
  • 1
Xtreme Biker
  • 28,480
  • 12
  • 120
  • 195

0 Answers0