jsf viewparam потерян после ошибки проверки

Я столкнулся со следующей проблемой: на одной странице я перечисляю всех пользователей моего приложения и имею кнопку «редактировать» для каждого из них, которая является ссылкой «ПОЛУЧИТЬ» с ?id=<userid>.

Страница редактирования имеет <f:viewParam name="id" value="#{editUserBean.id}"/> в метаданных.
Если я сделал некоторые ошибки ввода и отправки (я использую проверку CDI Weld Bean), страница отображается снова, но я потерял ?id=... в URL-адресе и, таким образом, потерял идентификатор пользователя, которого я редактирую .

Я просмотрел похожую проблему, описанную в ошибка проверки JSF, потеря значения, но решение со скрытым вводом (или, что еще хуже, с томагавком, который выглядит излишним) требует много уродливого кода.

Я попытался добавить «Разговор» с CDI, и он работает, но мне снова кажется, что это слишком много.

Существует ли простое решение в JSF для сохранения параметров представления в случае ошибок проверки?

[Моя среда: Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld) 1.1.2]


person DenisGL    schedule 30.07.2011    source источник
comment
вы используете includeViewParams=true в своем действии отправки?   -  person bluefoot    schedule 31.07.2011
comment
Нет, но мой обработчик действий не вызывается: отправка завершается ошибкой на этапе проверки.   -  person DenisGL    schedule 31.07.2011


Ответы (5)


Интересный случай. Для всех это воспроизводит следующий минимальный код:

Лицевая панель:

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

    <f:metadata>
        <f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
    </f:metadata>

    <h:body>

        <h:messages />

        #{viewParamBean.id} <br/>

        <h:form>
            <h:inputText value="#{viewParamBean.text}" >
                <f:validateLength minimum="2"/>
            </h:inputText>

            <h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
        </h:form>

    </h:body>
</html>

Бин:

@ManagedBean
@RequestScoped
public class ViewParamBean {

    private long id;    
    private String text;

    public void actionMethod() {

    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }    
}

Если вы вызовете Facelet с помощью viewparam.xhtml?id=12, на экране отобразится 12. Если вы затем введете что-то действительное, например. aaaaa идентификатор исчезнет из URL-адреса, но продолжит отображаться на экране (из-за природы компонентов пользовательского интерфейса с сохранением состояния).

Однако... как упоминалось в OP, как только произойдет какая-либо ошибка валидатора (например, ввод a), идентификатор будет безвозвратно потерян. Последующий ввод действительного ввода не вернет его обратно. Это почти похоже на ошибку, но я пробовал и Mojarra 2.1, и Myfaces 2.1, и обе ведут себя одинаково.

Обновление:

После некоторой проверки проблема, похоже, заключается в этом методе `UIViewParameter' (Mojarra):

public void encodeAll(FacesContext context) throws IOException {
    if (context == null) {
        throw new NullPointerException();
    }

    // if there is a value expression, update view parameter w/ latest value after render
    // QUESTION is it okay that a null string value may be suppressing the view parameter value?
    // ANSWER: I'm not sure.
    setSubmittedValue(getStringValue(context));
}

А затем более конкретно этот метод:

public String getStringValue(FacesContext context) {
    String result = null;
    if (hasValueExpression()) {
        result = getStringValueFromModel(context);
    } else {
        result = (null != rawValue) ? rawValue : (String) getValue();
    }
    return result;
}

Поскольку hasValueExpression() истинно, он попытается получить значение из модели (бэк-бина). Но поскольку этот bean-компонент находится в области запроса, он не будет иметь никакого значения для этого запроса, поскольку проверка только что завершилась неудачно, и, следовательно, значение никогда не устанавливалось. По сути, значение с сохранением состояния UIViewParameter перезаписывается тем, что вспомогательный компонент возвращает по умолчанию (обычно это значение null, но, конечно, это зависит от вашего компонента).

Одним из обходных путей является создание bean-компонента @ViewScoped, что в любом случае часто является лучшей областью действия (я предполагаю, что вы используете параметр для получения пользователя из службы, и, возможно, нет необходимости делать это снова и снова при каждой обратной передаче).

Другой альтернативой является создание собственной версии UIViewParameter, которая не пытается получить значение из модели, если проверка не удалась (как это делают все остальные компоненты UIInput).

person Arjan Tijms    schedule 31.07.2011
comment
Для интересующихся; Я создаю задачу для этого по адресу: java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1029 Пожалуйста, проголосуйте за это, если вы хотите, чтобы это было исправлено. - person Arjan Tijms; 03.08.2011
comment
Я заметил еще одно странное поведение, связанное с этой проблемой (с Mojarra (2.1.2), но не с MyFaces (2.1.0)): если для параметра просмотра установлено значение required="true", запрос Ajax завершится ошибкой на этапе проверки. - person DenisGL; 05.08.2011
comment
К вашему сведению: исправленное UIViewParameter доступно как <o:viewParam> в OmniFaces. См. также showcase.omnifaces.org/components/viewParam. - person BalusC; 11.02.2013
comment
Большое спасибо за ваше расследование! Я потерял довольно много времени, пытаясь понять, что не так с моей формой, пока не нашел этот вопрос. Изменение области действия bean-компонента помогло, но я также собираюсь обновить свой OmniFaces Jar. - person Med; 19.04.2013

На самом деле вы не теряете параметр просмотра. f:viewParam сохраняет состояние, поэтому, даже если его нет в URL-адресе, он все равно там. Просто поставьте точку останова или system.out в установщике, связанном с параметром просмотра.

(если вы погуглите viewParam без сохранения состояния, вы найдете дополнительную информацию)

person Mike Braun    schedule 30.07.2011
comment
Большое спасибо, я нашел интересную статью здесь ссылка. Тем не менее, у меня все еще есть проблема: если отправить мою форму без какой-либо ошибки, вызывается установщик, связанный с параметром представления, но если я сделаю ошибку при вводе и снова отправлю, значение будет потеряно. - person DenisGL; 31.07.2011
comment
UIViewParameter действительно имеет состояние, как я описал в этой статье, но из-за проблемного кода в методе encode этого компонента он перезаписывает его значением по умолчанию, которое находится в вспомогательном компоненте. Смотрите мой обновленный ответ. - person Arjan Tijms; 31.07.2011
comment
Хорошо, обычно он сохраняет состояние, но из-за некоторого недосмотра в дизайне значение действительно теряется всякий раз, когда возникают ошибки проверки. Кажется, я узнал что-то новое! - person Mike Braun; 31.07.2011

У меня то же самое в моем приложении. Я переключился на @ViewAccessScoped, что позволяет реализовать более элегантные реализации.

person Dar Whi    schedule 21.08.2011

 <f:metadata>
        <f:viewParam id="id" name="id" value="#{baen.id}"/>
    </f:metadata>

Или, когда вы впервые получаете параметр из URL-адреса, сохраните его в карте сеанса и продолжите использование с этой карты, а после сохранения/или обновления формы очистите карту.

person Armen Arzumanyan    schedule 21.12.2013

Это сложно, но вы можете попробовать восстановить параметры представления с помощью History API:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <f:metadata >
        <f:viewParam name="param1" value="#{backingBean.viewParam1}" />
        <f:viewParam name="param2" value="#{backingBean.viewParam2}" />
        <f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
    </f:metadata>
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <ui:fragment rendered="#{facesContext.postback}" >  
            <script type="text/javascript">
                var url = '?#{view.viewMap.get('queryString')}';
                history.replaceState({}, document.title, url);
            </script>
        </ui:fragment>
        <h:form>
            <h:inputText id="name" value="#{backingBean.name}" />
            <h:message for="name" style="color: red" />
            <br />
            <h:commandButton value="go" action="#{backingBean.go}" />
        </h:form>
        <h:messages globalOnly="true" />
    </h:body>
</html>
person Community    schedule 04.04.2016