无论数据是否有效,是否可以让 JSF 恢复用户输入的值?

Is it possible to make JSF restore user entered values regardless of the data being valid?

我正在开发一个具有表单的 JSF 应用程序:

<h:inputText value="#{model.firstname}" />
<h:inputText value="#{model.employeeNumber}" converter="javax.faces.Integer"/>
...

这是带有 bean 验证的模型:

@NotNull(message="Please enter a surname")
private String firstname;

@Min(value=1)
@Max(value=2000)
private Integer employeeNumber;
...
//setters and getters

除了页面中明确编码的 'back' 按钮(转到上一页/表单)之外,一切都运行良好。

我希望用户输入的值在他们再次 return 到上面的页面时恢复,无论数据是否 valid.for 例如,如果用户输入 abc employeeNumber 字段此字符串无法存储到模型上的 Integer

我知道 JSF 将用户输入的值存储到每个 UIComponent 的 "Request Values" 中。我想恢复的是这些而不是我的模型,因为上面的表格还没有验证它的数据。

我该怎么做?

(用户点击提交时会进行数据验证)。

我过去处理过这个问题 - 通过不使用 @NotNull@Min@Max 等验证注释。使用这些注释时,无法将无效数据应用于模型,因此无法在服务器上保存状态。

相反,我不得不在“提交”按钮后面的方法中编写验证逻辑代码。缺点是 JSF 不为您做这些工作;你必须自己做。好处是您可以更好地控制应用验证的确切时间和方式。

My colleague has just mentioned there may be a way to accomplish this using omnifaces

截至目前,有。 OmniFaces 中已经提供了很多东西,只是缺少了一小部分关键部分。我只是 committed a Hacks#getStateHelper() to 2.3 SNAPSHOT which should expose the protected UIComponent#getStateHelper() method into public. Then, it's doable together with help of EditableValueHolderStateHelper already in OmniFaces since 1.0 and with <o:form> and <o:ignoreValidationFailed> 以便无论如何调用操作,而不管 conversion/validation 错误(正如 "back" 按钮应该做的那样)。

因此,如果您确保至少使用 OmniFaces 2.3(目前仅作为 SNAPSHOT 提供),那么您可以使用以下会话范围的帮助程序 bean 实现要求,利用几个 OmniFaces 实用程序 classes FacesHacksEditableValueHolderStateHelper

@Named
@SessionScoped
public class Forms implements Serializable {

    private transient Map<String, StateHelper> states = new ConcurrentHashMap<>();

    public void saveState(ComponentSystemEvent event) {
        UIComponent form = event.getComponent();
        FacesContext context = Faces.getContext();
        StateHelper state = Hacks.getStateHelper(form);
        EditableValueHolderStateHelper.save(context, state, form.getFacetsAndChildren());
        states.put(Faces.getViewId(), state);
    }

    public void restoreState(ComponentSystemEvent event) {
        StateHelper state = states.get(Faces.getViewId());

        if (state != null) {
            UIComponent form = event.getComponent();
            FacesContext context = Faces.getContext();
            EditableValueHolderStateHelper.restore(context, state, form.getFacetsAndChildren());
        }
    }

    public void removeState() {
        states.remove(Faces.getViewId());
    }

}

需要在表单组件的postValidate事件中调用saveStaterestoreState()需要在表单组件的postAddToView事件中被调用。 removeState() 需要在成功操作期间调用。下面是一个示例表格:

<o:form>
    <f:event type="postAddToView" listener="#{forms.restoreState}" />
    <f:event type="postValidate" listener="#{forms.saveState}" />

    <h:inputText value="#{bean.string}" required="true" />
    <h:inputText value="#{bean.integer}" required="true" />

    <h:commandButton value="save" actionListener="#{forms.removeState()}" action="#{bean.save}" />
    <h:commandButton value="back" action="#{bean.back}">
        <o:ignoreValidationFailed />
    </h:commandButton>

    <h:messages />
</o:form>

这种方法的主要优点是不需要对现有的验证规则和支持 bean 进行修改,从而保留了 JSF 和 BV 的所有优点。

确保清除服务器会话状态 and/or 增加 serialVersionUIDForms class 每当您更改关联表单的组件树结构时,否则您必须进行预检查 and/or 正确处理异常。给表单和输入组件一个固定的 ID 也是 strongly recommended.