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.