tags:

views:

1767

answers:

1

What is the correct way to have a boolean checkbox in each row in a JSF / RichFaces dataTable? I tried the following snippet:

<rich:dataTable id="customerList"
               var="_customer"
             value="#{customerList.resultList}"
          rendered="#{not empty customerList.resultList}" >
    <h:column>
        <h:selectBooleanCheckbox
            value="#{customerList.selectedCustomers[_customer.id]}" />
    </h:column>
...
</rich:dataTable>

I set up my customerList to have a Map<Integer, Boolean> selectedCustomers. Things seem to work well, except that apparently the checkbox is being mapped by an index of sort, not actually by the ID, and this is causing me a problem.

For example, when I open the page above and check the checkbox in the first row and press my "Delete" button, everything works fine, and the page is reloaded without the selected customer. But if I press "Refresh" or "Reload" then, (and accept the browser warning of resending data), the customer that is now in the first row gets deleted!

What should I do to have the checkbox tied to the selected ID only?

+2  A: 

If you only want to handle refresh, have a look at the redirect option in navigation rules.

  <navigation-rule>
    <display-name>navBack</display-name>
    <from-view-id>/navBack.jsp</from-view-id>
    <navigation-case>
      <from-outcome>navTo</from-outcome>
      <to-view-id>/navTo.jsp</to-view-id>
      <redirect />
    </navigation-case>
  </navigation-rule>

If you want to handle back-button and multiple window cases too, read on.

You could detect this using a hidden field and a row-level artifact. Basically, you check the id emitted to the client against the id received when the form is submitted.

public class SafeRow {
  private final String rowId;
  private String rowIdClient;
  private String name;
  private boolean delete;

  public SafeRow(String name) {
    this();
    this.name = name;
  }

  public SafeRow() {
    rowId = UUID.randomUUID().toString();
  }

  public String getRowId() { return rowId; }    
  public void setRowId(String rowIdClient) { this.rowIdClient = rowIdClient; }

  public boolean isStateConsistent() {
    return rowId.equals(rowIdClient);
  }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }

  public boolean isDelete() { return delete; }
  public void setDelete(boolean delete) { this.delete = delete; }
}

The table data:

public class SafeUpdateBean {
  private final List<SafeRow> data = new ArrayList<SafeRow>();

  public SafeUpdateBean() {
    data.add(new SafeRow("Bill"));
    data.add(new SafeRow("Ben"));
    data.add(new SafeRow("Sue"));
  }

  public List<SafeRow> getData() { return data; }

  public String deleteSelected() {
    Iterator<SafeRow> all = data.iterator();
    while (all.hasNext()) {
      SafeRow row = all.next();
      if (!row.isStateConsistent()) {
        System.err.println("Caught inconsistency on " + row.getRowId());
      } else if (row.isDelete()) {
        all.remove();
      }
    }
    return null;
  }
}

The view:

<h:form>
  <h:dataTable value="#{safeUpdateBean.data}" var="row">
    <h:column>
      <h:inputHidden value="#{row.rowId}" />
      <h:selectBooleanCheckbox value="#{row.delete}" />
      <h:outputText value="#{row.name}" />
    </h:column>
  </h:dataTable>
  <h:commandButton value="delete selected"
    action="#{safeUpdateBean.deleteSelected}" />
</h:form>

This demo code uses a simple session bean, but hopefully it is clear how you could adapt it to something more sensible. This isn't the only thing you could do - you could perform a more general duplicate form submission, for example. 3rd party frameworks like Seam also add handling for this sort of thing.

McDowell
Thank you. Although I am not very clear how this works (or what will cause the safe row state to be inconsistent), the note on duplicate form submission and Seam is helpful. I'll try Seam's `<s:token>`.
Hosam Aly
When the form is submitted, rows will be decoded by index. Since the id in the hidden field no longer matches the id at that index on the server, the business logic knows there is a problem. Making the id setter/getter work with different member variables is used to determine this.
McDowell
Alright, +1, thank you. But would it be easier if I just used normal HTML check boxes, and took them as a `List<String>` containing the IDs?
Hosam Aly
You'll probably have problems using HTML `<input type="checkbox" name="#{_c.someId}"` controls as `dataTable` children if you're using JSPs (you'd probably need a custom control). In Facelets, you'd have to manage identifying the IDs from the parameter map yourself (much as you would in a servlet).
McDowell