Search code examples
jsfrichfacesjsf-1.2seam2

Using a dataTable to manage a dynamic number of Strings


I've become stuck on this one:

    <rich:dataTable id="tabA" value="#{m.a}" rowKeyVar="nr" var="ex">
        <rich:column><h:outputText value="Exercise #{nr + 1}:" /></rich:column>
        <rich:column><h:inputText value="#{ex}" /></rich:column>
    </rich:dataTable>
    <h:panelGrid columns="2">
        <a4j:commandButton value="+" title="new" ajaxSingle="true"
            action="#{m.createNewExercise}" reRender="tabA"/>
        <a4j:commandButton value="-" title="remove last" ajaxSingle="true"
            action="#{m.deleteExercise}" reRender="tabA"/>
    </h:panelGrid>

Because this is part of a bigger form, I have set ajaxSingle="true".

The value I'm iterating over is a List of Strings, defined like this:

@Entity
public class M {
    ...
    private List<String> a = Lists.newArrayList();

    @CollectionOfElements
    public List<String> getA() { return a; }
    public void setA(List<String> a) { this.a = a; }
}

This fails to update the backing list a when submitted (submit is done via a simple <h:commandButton>). Since the content from the inputText elements is submitted, JSF seems failing to apply the values. Maybe someone can shed light on whether this is because of the @CollectionOfElements storage type in M.

So what I'm looking for is a way to save the ex values when calling createNewExercise or deleteExercise, either by not having to reRender the complete table or by sending them to the server first.

I can probably make it work via a binding like suggested here, but I'm interested in a way to avoid the overhead of a binding.


Solution

  • The problem is the ajaxSingle="true" attribute, combined with JSF's apparent inability to update a @CollectionOfElements.

    The description of ajaxSingle clearly states that it skips all model updates not belonging to it's component (which is not the dataTable nor it's value attribute). So this is classic PEBKAC.

    But this still doesn't work w/o ajaxSingle, which led me to believe the JSF side was OK when I was testing that option.

    What I ended up with was:

    <rich:dataTable id="tabA" value="#{m.a}" rowKeyVar="nr" var="ex">
        <rich:column><h:outputText value="Exercise #{nr + 1}:" /></rich:column>
        <rich:column><h:inputText value="#{ex}" /></rich:column>
    </rich:dataTable>
    <h:panelGrid columns="2">
        <a4j:commandButton value="+" title="new"
            action="#{m.createNewExercise}" reRender="tabA"/>
        <a4j:commandButton value="-" title="remove last"
            action="#{m.deleteExercise}" reRender="tabA"/>
    </h:panelGrid>
    

    And the Beans:

    @Entity
    public class M {
      ...
      private List<Exercise> a = Lists.newArrayList();
    
      @OneToMany(cascade = CascadeType.ALL)
      @JoinColumn(name = "ex_id")
      public List<Exercise> getA() { return a; }
      public void setA(List<Exercise> a) { this.a = a; }
    }
    
    @Entity
    public class Exercise {
      [id definition omitted]
      private M m;
      private String description;
    
      @ManyToOne
      @JoinColumn(name = "ex_id", insertable = false, updatable =false)
      public M getM() { return m; }
      public void setM(M m) { this.m = m; }
    
      public String getDescription() { return description; }
      public void setDescription(String d) { this.description = d; }
    }