Creé un ejemplo muy simple basado en mi proyecto para ilustrar mi duda. Solo una forma de registrar a una persona con una lista de números de teléfono.

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>

En mi ejemplo, tenga en cuenta que todos los campos de teléfono que se agregan DEBEN completarse (obligatorio = verdadero).

El problema es: cuando escribo nombre y hago clic en agregar (para agregar un teléfono), el valor del campo se mantiene. Pero cuando escribo un primer teléfono y hago clic en agregar , el valor del teléfono no se mantiene. Esto ocurre para todos los campos dentro del componente ui: repeat.

¿Existe alguna forma de conservar los valores de entrada dentro de un después de una solicitud inmediata, como con el campo nombre ?


Nota adicional: Otro comportamiento extraño que noté es cuando agrego al menos dos campos de teléfono, dejo el primero en blanco y llena el segundo, y guarda el formulario. Después de una validación fallida (debido a que el teléfono está en blanco), haga clic en agregar para que todos los campos se llenen con el valor del segundo teléfono.


Wildfly 9.0.2, JSF Api (Jboss) 2.2.12

2
Marcelo Barros 27 ene. 2016 a las 20:02

2 respuestas

La mejor respuesta

Gracias a @BalusC comentario. La biblioteca OmniFaces tiene dos controladores de etiquetas que se pueden utilizar en este caso. En ambos casos, los valores de entrada se conservarán en caso de falla de validación. Tenga en cuenta que h: commandButton debe estar con <h:commandButton immediate="false" />.

ignoreValidationFailed

En este caso, se ignorarán todas las fallas de validación (incluidas las fallas del convertidor). Tenga en cuenta que la forma h: debe cambiarse a forma o:. Además, los mensajes de fallas se seguirán mostrando, lo que se puede resolver poniendo una condición adecuada en el atributo renderizado. Los archivos se verán así:

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

En este caso, solo se ignorarán las fallas de validación (los convertidores aún se ejecutarán). Los mensajes de fallas no se mostrarán, excepto para los convertidores. Tenga en cuenta que este controlador de etiquetas solo está disponible desde la versión 2.3. Los archivos se verán así:

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>
3
Community 23 may. 2017 a las 12:31

La solución que utilizo para este problema es crear un campo externo al bucle, que almacena un JSON que contiene los valores que deben guardarse. Este campo, para estar fuera del ciclo, guarda correctamente los valores después de cada intento y restaura los valores faltantes cuando es necesario. Utilizo dos funciones JavaScript y la biblioteca JQuery.

Entonces los archivos se verían así:

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]; });
    }
}

Aunque funciona perfectamente (incluso a través de ajax, con algunos cambios menores), parece un truco, no una solución ideal. Entonces, si alguien puede ayudar con cualquier solución estrictamente basada en JSF, se lo agradeceré. Gracias.

0
Marcelo Barros 10 feb. 2016 a las 15:13