1
votes

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;
}
}
1

1 Answers

0
votes

Your problem lies in this row:

 <h:commandLink actionListener="#{testBean.edit(status.index)}">

You can't send arguments to actionlisteners this way, that's not how it works. You need to change that row to something like:

<h:commandLink actionListener="#{testBean.edit}" customerIndex="#{status.index}>

And then change the edit method to something like this.

public void edit(ActionEvent ae) {
    int index = ae.getComponent().getAttributes().get("customerIndex");
    System.out.println("Called edit with index: " + index);
    customerEdit = new Customer(customers.get(index).getName());
}

Also I'm not sure how your "save" method relates to anything else, but that's probably just because you skipped some irrelevant code.

EDIT: You can send arguments this way if it's a javascript method, but not to managed beans or anything else inside the #{} tags.