views:

347

answers:

1

Hi,

I have a strange problem with the <ui:repeat> tag. Even for my very simple example, value bindings inside nested repeat components do not work as expected.

I have a simple facelet like so:

<h:body>
<h:form>
<ui:repeat value="#{sandbox.rows}" var="row">
    <ui:repeat value="#{row.columns}" var="column">
        <h:outputText value="#{column.value}" />
        <h:selectBooleanCheckbox value="#{column.value}" />
    </ui:repeat>
    <br/>
</ui:repeat>

<h:commandButton action="#{sandbox.refresh}" value="Refresh" />
</h:form>
</h:body>

and a Sandbox class:

@Component
@Scope("request")
public class Sandbox {

    public static class Row {
        private List<Column> columns = Arrays.asList(new Column(), new Column());
        public List<Column> getColumns() {
            return columns;
        }
    }

    public static class Column {
        private boolean value;
        public void setValue(boolean value) {
            this.value = value;
        }
        public boolean getValue() {
            return this.value;
        }
    }

    public List<Row> rows = Arrays.asList(new Row(), new Row()); 

    public List<Row> getRows() {
        return rows;
    }

    public void refresh() {
        rows.get(0).getColumns().get(0).setValue(true);
        System.err.println("refresh clicked");
    }
}

So my facelet loops over the "rows" in sandbox, which has a number of "columns", which each has a value. For each such column, the value is printed, and a <h:selectBooleanCheckbox> with the value bound to it is output.

When I load the page, all values are displayed as false, and all checkboxes are unchecked. Now, clicking refresh is supposed to alter the value of the first column of the first row to true. However, I get the following output:

true [ ] false [ ]
false [ ] false [ ]

In other words, the <h:outputText> displays it as true, but the checkbox is not checked. Certainly I am allowed to change the model in the invoke application phase and that should be reflected when rendering the view?

If I remove one level of nesting, so there is only one <ui:repeat>, everything works as expected: the checkbox is checked and the value is displayed as true. So this appears to have something to do with the UIRepeat component. In fact, it seems like UIRepeat has some special handling for when it's nested inside another UIRepeat.

From what I gather, UIRepeat essentially rerenders the same component several times. Between each call to render, it loads the "state" (value, localValue, submittedValue) of all child components implementing EditableValueHolder from an internal map (keyed on the rendered component's actual id). I've tried putting break points when this occurs to track what value is inserted into the map of saved states, but it's really a mess, as the saveChildState and restoreChildState methods are called about a gazillion times.

Any ideas? Can I go about this differently? What I really need is be able to render a table that grows dynamically both horizontally and vertically, containing checkboxes, input fields, etc. I glanced at <h:dataTable> but I believe it won't work in this case.

+1  A: 

Interesting issue. I can reproduce this with Mojarra 2.0.3. It's definitely a problem in state saving of ui:repeat. I've reported it as issue 1807 to the Mojarra guys. It works by the way fine when the outer loop is a c:forEach.

BalusC
My outer loop cannot be a c:forEach, because in my real application it's all embedded inside a composite component.
waxwing
Once again, I am amazed by your knowledge and helpfulness. Thanks! I will eagerly track that issue to see what they say.
waxwing
Still, it *can* be. What's the problem you have with it? Edit: no problem, you're welcome.
BalusC
I guess maybe that should be a separate problem. The essence of it is: I have a composite component (a Grid) that has a "rows" attribute, which is used to draw the table as in the example. As far as I understand it, the value of "rows" will not be available when building the component tree, which makes c:forEach loop forever.
waxwing