JSF 2 - Bean Проверка: не удалось выполнить проверку → пустые значения заменяются последними действительными значениями из управляемого bean


Я не понимаю поведение JSF2 во время valdation. Надеюсь, кто-нибудь может мне помочь.

У меня есть форма, где поля проверяются после (ajax) submit - ok
Если проверка не выполнена, отображается сообщение об ошибке - ok

В моем примере, когда я введу действующий день рождения и имя поля пуст, после отправки отображается сообщение об ошибке. Теперь, когда я ввожу действительное имя и удаляю входные данные из поля дня рождения, errormessage отображается для дня рождения (это нормально), но теперь старый "действительный" день рождения стоит также в поле ввода!?!

Как я могу избежать такого поведения? Когда я отправляю пустое поле, я хочу видеть errormessage и пустое поле...

Вот мой пример кода:

Я использую ManagedBean (TestBean), который содержит EntityBean (Contact). Контакт содержит валидации на аннотации.

public class Contact implements Serializable {
    @NotNull 
    @Temporal(TemporalType.DATE)
    private Date birthday;

    @NotNull 
    @Size(min=3, max=15)
    private String name;

    //...
}

Мой ManagedBean:

@ManagedBean
@ViewScoped
public class TestBean implements Serializable {
    private Contact contact;

    @PostConstruct
    void init() {
        System.out.println("init...");
        contact = new Contact(); 
    }

    public void newContact(ActionEvent ae) {
        System.out.println("newContact...");
        contact = new Contact();
    }

    public void save() {
        System.out.println("save...");
        //TODO do something with contact...
    }

    public Contact getContact() { return contact; }

    public void setContact(Contact contact) {this.contact = contact;}
}

Здесь моя страница JSF:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core" >      
 <h:body>
    <h:form>     
        <h:panelGrid columns="3">   

        <h:outputText value="Birthday: " />  
        <h:inputText id="birthday" value="#{testBean.contact.birthday}">
            <f:convertDateTime/>
        </h:inputText>  
        <h:message for="birthday" />  

        <h:outputText value="Name: " />  
        <h:inputText id="name" value="#{testBean.contact.name}"/>
        <h:message for="name" />

        </h:panelGrid>  

        <h:commandButton value="submit" action="#{testBean.save}"> 
            <f:ajax execute="@form" render="@form"/>
        </h:commandButton> 

        <h:commandButton value="newContact" actionListener="#{testBean.newContact}"
                         immediate="true"> 
            <f:ajax render="@form"/>
        </h:commandButton> 

    </h:form>
</h:body>
</html>

наконец-то фрагмент из web.xml

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

<context-param>
    <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
    <param-value>true</param-value>
</context-param>

Спасибо за некоторые советы

Ответ 1

Ваша конкретная проблема вызвана

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

и ошибка (по крайней мере, надзор) в HtmlBasicRenderer#getCurrentValue() Моджары:

if (component instanceof UIInput) {
    Object submittedValue = ((UIInput) component).getSubmittedValue();
    if (submittedValue != null) {
        // value may not be a String...
        return submittedValue.toString();
    }
}

String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null) {
    currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;

Обычно переданное значение устанавливается на null, когда компонент UIInput успешно преобразован и проверен. Когда JSF собирается повторно отобразить значение, он сначала проверяет, не было ли переданное значение не null, прежде чем переходить к отображению значения модели. Однако с этим контекстным параметром вместо пустой строки, если она недействительна, она null, и поэтому она всегда будет отображать исходное значение модели при удалении начального значения требуемого поля.

Чтобы проверить его, установите значение параметра контекста в false или полностью удалите его. Вы увидите, что он работает по назначению. Однако это приведет к тому, что ваши значения модели будут загромождены пустыми строками в пустых, но необязательных полях, и вы потеряете преимущество использования аннотации @NotNull проверки JSR 303 bean.

Чтобы исправить это, вы должны изменить первую часть HtmlBasicRenderer#getCurrentValue() следующим образом:

if (component instanceof UIInput && !((UIInput) component).isValid()) {
    Object submittedValue = ((UIInput) component).getSubmittedValue();
    if (submittedValue != null) {
        // value may not be a String...
        return submittedValue.toString();
    } else {
        return null;
    }
}

Я уже сообщал об этом ребятам Mojarra как issue 2262.

Ответ 2

Я думаю, что при нормальном использовании люди не будут вводить действительную дату, отправить, а затем удалить дату перед отправкой снова. Я понимаю, что вы нашли это во время тестирования, но, вероятно, люди просто пытаются успешно заполнить форму, а не удалять вещи, которые они уже ввели, и в этом случае, возможно, сохранение последнего действительного значения - лучшая функциональность.

Если вы настаиваете... Кажется, что метод setter "день рождения" никогда не вызывается, потому что значение недопустимо, а затем, когда страница перерисовывается, отображается текущее значение "день рождения" (текущее значение является действительным которое ранее было сохранено). Возможно, вы могли бы написать специальный валидатор, который устанавливает значение, и THEN проверяет значение, но это не имеет особого смысла. Вам все равно придется сначала проверять значение для случаев, когда пользователи вводят текстовую строку, например "вчера", вместо допустимой даты, а затем вам нужно будет установить дату на что-то на основании этого недопустимого значения, а затем у вас будет для добавления сообщения в FacesContext. Таким образом, код должен был бы сделать что-то вроде следующего.

1) проверить значение даты
2) если он недействителен, установите для поля какое-то значение, которое имеет смысл и добавляет сообщение об ошибке в FacesContext.
3) если он действителен, то используйте его.

Это делает, но странно, потому что вы меняете значение поля, хотя значение переданного значения недопустимо...

Ответ 3

Аарон уже описывает поведение.

Проблема, о которой я описал, существует также нажатием кнопки "newContact". Если первый запрос недействителен (введен день рождения, поле имени пуст), отображается сообщение об ошибке. ок.

Впоследствии кнопка 'newContact' не обновляет (очищает) представление. Хотя модель была сброшена (контакт = новый контакт()).

Я нашел несколько подсказок здесь: http://wiki.apache.org/myfaces/ClearInputComponents

Вот мое решение:

public void newContact(ActionEvent ae) {
    contact = new Contact();
    contact.setBirthday(new Date()); //for testing only

    resetForm(ae.getComponent());
}

private void resetForm(UIComponent uiComponent) {
    //get form component
    UIComponent parentComponent = uiComponent.getParent();
    if (uiComponent instanceof UIForm)
        resetFields(uiComponent);
    else if (parentComponent != null)
        resetForm(parentComponent);
    else
        resetFields(uiComponent);

}

private void resetFields(UIComponent baseComponent) {
    for (UIComponent c : baseComponent.getChildren()) {
        if (c.getChildCount() > 0)
            resetFields(c);

        if (c instanceof UIInput)
            ((UIInput) c).resetValue();
    }
}

Ответ 4

Была аналогичная проблема, когда значение, загруженное из резервной копии bean, получило бы reset, когда поле было заглушено, а другой компонент не прошел проверку. Мне пришлось сделать небольшое дополнение к коду BalusC, чтобы он работал.

protected String getCurrentValue(FacesContext context,
                                     UIComponent component) {

        if (component instanceof UIInput && !((UIInput) component).isValid()) {
            Object submittedValue = ((UIInput) component).getSubmittedValue();
            if (submittedValue != null) {
                // value may not be a String...
                return submittedValue.toString();
            } else {
                return null;
            }
        }


        String currentValue = null;
        Object currentObj;

        if ( component instanceof UIInput && ((UIInput)component).isLocalValueSet() )
        {
           currentObj = ((UIInput)component).getLocalValue();
        }
        else {
            currentObj = getValue(component);
        }

        if (currentObj != null) {
            currentValue = getFormattedValue(context, component, currentObj);
        }
        return currentValue;


    }