2

I created a very simple example based on my project in order to illustrate my doubt. Just a way to register a person with a list of telephone numbers.

MainController.java

private String name;
private List<Phone> phoneList;
// Getters and Setters

@PostConstruct
private void init() {
    phoneList = new ArrayList<>();
}

public static class Phone implements Serializable {

    private String number;
    // Getters and Setters

    @Override
    public String toString() {
        return number != null ? number : "null";
    }
}

public void add() {
    phoneList.add(new Phone());
}

public void save() {
    System.out.println("Name: " + name + "; " + phoneList.toString());
}

index.xhtml

<h:form>
    <h:inputText value="#{mainController.name}" required="true" />
    <ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
        <h:inputText value="#{phone.number}" required="true" />
    </ui:repeat>
    <h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
    <h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>

In my example, note that all phone fields that are added MUST be filled in (required = true).

The problem is: when I type name and click add (to add a phone) the value of the field is maintained. But when I type a first phone and click add, the phone's value is not maintained. This occurs for all fields within the component ui:repeat.

Is there a way to preserve the input values within a after an immediate request, as with the name field?


Extra note: Other strange behavior I noticed is when add at least two phone fields, let the first blank and fills the second, and saves the form. After a failed validation (due to phone blank), click add will make all fields are filled with the value of the second phone.


Wildfly 9.0.2, JSF Api (Jboss) 2.2.12

Marcelo Barros
  • 930
  • 9
  • 16
  • 1
    Is this helpful? http://stackoverflow.com/q/8370675 – BalusC Jan 27 '16 at 20:40
  • Thanks @BalusC It really helps for this example, using binding and required. But in my real application this approach doesn't work because it doesn't apply to validator and converter (even using f:validator, f:converter and disabled property, which behaves differently from the required property). I simplified as much as possible to display a question as clear and simple. If you think I should put more requirements in question I can change it. – Marcelo Barros Jan 27 '16 at 22:11
  • 1
    Is this more helpful? http://snapshot.omnifaces.org/taghandlers/skipValidators – BalusC Feb 10 '16 at 15:22
  • I didn't know this taghandler - I use OmniFaces 2.2 ;-). It can be perfectly used in the case, noting that the converter will still run as the [docs](http://snapshot.omnifaces.org/taghandlers/skipValidators) says. Anyway, through your comment I noticed that there is a `ignoreValidationFailed` that (with some care rendering messages) can also be used. Thanks again @BalusC, I'll try to make an answer based on your comment. – Marcelo Barros Feb 10 '16 at 17:21
  • About **Extra Note**, the problem persists even using `skipValidators` or `ignoreValidationFailed`. I'll post another question about it, since it changes the main topic. I think it's a bug in JSF. – Marcelo Barros Feb 10 '16 at 17:28

2 Answers2

3

Thanks to @BalusC comment. The OmniFaces library has two taghandlers that can be used in this case. In both cases input values will be preserved in case of validation failure. Note that h:commandButton should be with <h:commandButton immediate="false" />.

ignoreValidationFailed

In this case all validation failures will be ignored (including converter failures). Note that the h:form have to be changed to o:form. Also, the failures messages will still be displayed, which can be solved putting a proper condition in the rendered attribute. The files will look like this:

index.xhtml

<o:form>
    <h:inputText value="#{mainController.name}" required="true" />
    <ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
        <h:inputText value="#{phone.number}" required="true" />
    </ui:repeat>
    <h:commandButton action="#{mainController.add()}" value="Add Phone">
        <o:ignoreValidationFailed />
    </h:commandButton>
    <h:commandButton action="#{mainController.save()}" value="Save" />
</o:form>
<h:messages rendered="#{facesContext.validationFailed}" />

skipValidators

In this case only the validation failures will be ignored (the converters will still run). The failures messages will not be displayed, except for the converters. Note that this taghandler is only available since the 2.3 version. The files will look like this:

index.xhtml

<h:form>
    <h:inputText value="#{mainController.name}" required="true" />
    <ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
        <h:inputText value="#{phone.number}" required="true" />
    </ui:repeat>
    <h:commandButton action="#{mainController.add()}" value="Add Phone">
        <o:skipValidators />
    </h:commandButton>
    <h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
Community
  • 1
  • 1
Marcelo Barros
  • 930
  • 9
  • 16
0

The solution that I use to this problem is to create an external field to the loop, which stores a JSON containing the values that should be saved. This field, to be outside the loop, properly saves values after each try and restore the missing values when necessary. I use two functions JavaScript and JQuery library.

So the files would look like this:

index.xhtml

<h:outputScript library="jquery" name="jquery.min.js" />
<h:outputScript library="all" name="all.js" />

<h:form>
    <h:inputText value="#{mainController.name}" required="true" />
    <ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
        <h:inputText styleClass="savePhoneNumber" value="#{phone.number}" required="true" onchange="saveUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')" />
    </ui:repeat>

    <h:inputHidden id="allPhoneNumber" binding="#{allPhoneNumber}" />
    <h:outputScript>loadUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')</h:outputScript>

    <h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
    <h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>

all.js

function saveUiRepeatInput(inputAll, inputClass) {
    document.getElementById(inputAll).value = JSON.stringify($('.' + inputClass).map(function() { return this.value; }).get());
}

function loadUiRepeatInput(inputAll, inputClass) {
    var jsonAll = document.getElementById(inputAll).value;
    if (jsonAll) {
        var array = JSON.parse(jsonAll);
        $('.' + inputClass).each(function(i) { if (i < array.length) this.value = array[i]; });
    }
}

Although work perfectly (including via ajax, with some minor changes), it looks like a hack, not an ideal solution. So if anyone can help with any solution strictly based on JSF, I will be grateful. Thanks.

Marcelo Barros
  • 930
  • 9
  • 16