2

I've got a JSF page with a ui:repeater tag that simply displays a list of strings and some controls to add a string to a list. When adding a string I use ajax to update the repeater tag and have the new string be shown immediately without the page refresh. Here's how my page looks like:

<h:body>
    <h:form>
        <p:inputText id="name" value="#{testController.newString}"/>
        <p:commandButton value="Add" actionListener="#{testController.addString}" update="strings" />
    </h:form>       

    <h:panelGroup id="strings">         
        <ui:repeat var="str" value="#{stringModel.strings}" varStatus="stringData">                                                                                                             
            <div>                                       
                <h:outputText value="#{str}" />
                <h:inputText value="#{str}" />
            </div>                                                                                  
        </ui:repeat>
    </h:panelGroup>                              

</h:body>

Everything works except the inputText component. After ui-repeater is updated with Ajax is still displays the text from the previous string. For example, assume that initially i have a list with 2 strings, "val1" and "val2". I enter a new string called "val3" and submit the form. List is updated correctly on the server side and the repeater is updated, it now has 3 elements. However, while the h:outputText in the newly added element will correctly show "val3", the inputText will be displayed with "val2" as a value. So i end up with something looking like this:

output tag     input tag
val1           val1
val2           val2
val3           val2 (???)

The backing beans are very simple: A view scoped model bean

@Component
@Scope("view")
public class StringModel {

    private List<String> strings = Lists.newArrayList("Value 1");

    public List<String> getStrings() {
        return strings;
    }

    public void setStrings(List<String> strings) {
        this.strings = strings;
    }   
}

And a request scoped controller bean:

@Component
@Scope("request")
public class TestController {

    private String newString;
    @Autowired private StringModel model;

    public void addString() {
        model.getStrings().add(newString);
    }

    public String getNewString() {
        return newString;
    }

    public void setNewString(String newString) {
        this.newString = newString;
    }   

}

I did some testing and this actually works the same way for any input component, be that textInput, textArea, etc. Any help would be highly appreciated.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Alex
  • 23
  • 1
  • 3
  • It makes no sense. You are outputting the exact same #{str} twice. How could it change? Can you try printing #{str} a third time see what it gives? – toto2 Aug 13 '11 at 15:21
  • Problem's not in the number of times I print it out, but rather in how JSF handles input elements inside ui:repeat. I can print it out 10 times and all input components will have wrong value, while output ones won't. – Alex Aug 13 '11 at 18:10
  • I could not believe you had pasted the right code. I asked you to try print a third value since I thought you might see a mistake somewhere. Apparently you are not the problem but it is this aberrant bug instead... – toto2 Aug 13 '11 at 18:32

3 Answers3

6

I can't tell in detail exactly why it displays the wrong value after update (it'll be that the internal loop index of <ui:repeat> is broken — try a newer Mojarra version), but just referencing the string item by index from varStatus works. It'll also immediately fix the future problem of being unable to submit the edited string value when you put this list in a form, because the String class is immutable and doesn't have a setter.

<ui:repeat value="#{stringModel.strings}" var="str" varStatus="loop">
    <div>
        <h:outputText value="#{str}" />
        <h:inputText value="#{stringModel.strings[loop.index]}" />
    </div>
</ui:repeat>
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Indeed this works. Thanks for pointing this out - i've spent a day on this but it never crossed my mind to try it like that. (as for setting a string, in reality i have an object there, i've just stripped it down to the simplest possible example to illustrate the problem). – Alex Aug 13 '11 at 18:19
2

EditableValueHolders inside ui:repeat are broken (by design) in the current version o JSF specs. It will not work, there is no way to fix it. Maybe new versions will make ui:repeat a proper component with support for saving states of its children. Maybe not.

If you change ui:repeat to h:dataTable, things should work (if not, then your problem is somewhere else and I was wrong).

Frankly, there is no workaround apart from using repeaters from some other libraries - you should find working repeaters in Tomahawk, Trinidad and many other places. Primefaces, AFAIR, does not have a pure repeater.

fdreger
  • 12,264
  • 1
  • 36
  • 42
  • @toto: Seems to me it is a bit by design. There is a certain reluctance surrounding the problem, as if the developers thought that fixing it goes against the JSF architecture (as is in the case of multi-field validation, where I strongly agree with them). The problems with repeater/uiinputare well known for a lot of time (a lot of bugs opened in Mojarra, some are two years and two major releases old), and yet nothing changes. – fdreger Aug 13 '11 at 17:55
  • Hey, I just wanted to say that this answer was really helpful- it convinced me that I wasn't crazy. Replacing my a4j:repeat (which uses ui:repeat) with a c:forEach solved my problem. Thanks. – evan Apr 02 '12 at 22:23
0

I also had exactly the same problem before. I solved it by putting the inputText in a form. I also copied your codes and put the h:inputText inside a h:form and it worked as well.

<h:form>
    <ui:repeat value="#{stringModel.strings}" var="str" varStatus="loop">
        <div>
            <h:outputText value="#{str}" />
            <h:inputText value="#{str}" />
        </div>
    </ui:repeat>
</h:form>
Wang Sheng
  • 780
  • 1
  • 6
  • 18
  • 1
    The form presentation will work well, but the form submit won't work well (for the very simple reason that the `String` class is immutable and doesn't have a setter method). How to fix that as well, see my answer. – BalusC Feb 03 '13 at 10:48