I'm having trouble understanding why my getters aren't being called when I expect them inside a ui:repeat. I'm using Glassfish 3.1.1 / Mojarra 2.1.3.
The code below will render a table like
Alice [empty input] [empty output] [link: update value] [link: cancel]
Bob [empty input] [empty output] [link: update value] [link: cancel]
If I click "update value" on the Alice row, then "update value" on the "Bob" row, I end up with this:
Alice [Alice] Alice
Bob [Alice] Bob
I don't understand why the output for the "Bob" row is picking up "Alice" instead. It's like the getter isn't being called during the render-response phase, and instead the old value from the managed bean is stuck to the UIInput in the update-model-values phase.
What's weird is that if I hit "update value" on the Alice row, then "cancel", then "update value" on the Bob row, I get the expected result.
Also, if I add "@form" to the render=... on the "update value" link, I will see the right values (although they will be duplicated on each row). I don't like this as much, primarily because I don't want to update the whole table to process a single row.
What could be causing this? What am I missing about the JSF lifecycle?
Also - the same pattern works just fine outside of a ui:repeat. In that case, the h:inputText seems to always refresh with the right value from the managed bean, calling the getter in the "render response" phase as I expect.
This was originally using PrimeFaces p:commandLink but I get exactly the same behavior with standard JSF h:commandLink and f:ajax.
Also I'm aware of PrimeFaces row editor and that would possibly be a better solution to the general overall problem - I still want to understand why this doesn't work though.
Thanks!
The relevant XHTML is as follows
<h:form id="testForm">
<table style="width:400px; ">
<ui:repeat value="#{testBean.customers}" var="customer" varStatus="status">
<tr>
<td><h:outputText id="output" value="#{customer.name}"/></td>
<td><h:inputText id="input" value="#{testBean.customerEdit.name}"/></td>
<td><h:outputText id="otherOutput" value="#{testBean.customerEdit.name}"/></td>
<td>
<h:commandLink actionListener="#{testBean.edit(status.index)}">
<f:ajax execute="@this" render="input otherOutput"/>
Update value
</h:commandLink>
<h:commandLink actionListener="#{testBean.cancel}">
<f:ajax execute="@this" render="input otherOutput"/>
Cancel
</h:commandLink>
</td>
</tr>
</ui:repeat>
</table>
</h:form>
The "testBean" managed bean is view-scoped:
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ViewScoped
@ManagedBean
public class TestBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
System.out.println("returning name: " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
}
private List<Customer> customers;
private Customer customerEdit = new Customer(null);
@PostConstruct
public void init() {
customers = Arrays.asList(new Customer("Alice"),
new Customer("Bob"), new Customer("Carol"), new Customer("David"), new Customer("Eve"));
}
public Customer getCustomerEdit() {
return customerEdit;
}
public void setCustomerEdit(Customer customerEdit) {
this.customerEdit = customerEdit;
}
public void edit(int index) {
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
public void save(int index) {
System.out.println("Called save with index: " + index + " new name = " + customerEdit.getName());
customers.set(index, customerEdit);
customerEdit = null;
}
public void cancel() {
System.out.println("Called cancel");
customerEdit = null;
}
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
}