【问题标题】:Is it possible to make JSF restore user entered values regardless of the data being valid?无论数据是否有效,是否可以让 JSF 恢复用户输入的值?
【发布时间】:2026-02-17 23:20:08
【问题描述】:

我正在开发一个带有表单的 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

除了页面中显式编码的“后退”按钮转到上一页/表单之外,一切都运行良好。

我希望用户输入的值在他们再次返回上述页面时恢复,无论数据是否有效。例如,如果用户在employeeNumber字段中输入abc,则此字符串无法存储到模型上的Integer

我了解 JSF 将用户输入的值存储到每个 UIComponent 的“请求值”中。我想恢复的是这些,而不是我的模型,因为上面的表格还没有验证它的数据。

我该怎么做?

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

【问题讨论】:

  • 不就是浏览器的autocomplete吗?如果您不想要这个,请添加 autocomplete="off"
  • 嗨,不,这与我想要的相反。我希望数据重新出现在用户已部分填充并导航离开的页面上,而无需验证该数据,然后返回。好像用户从未离开过,可以继续填写表单然后进行验证。
  • 您可以使用表单bean(带有字符串)来存储用户数据并在提交操作时将数据保存到模型中。如果表单 bean 具有会话范围,那么它应该可以按预期工作。
  • 这可以很容易地在客户端完成,例如HTML5 local storage。这可以接受吗?如果你真的想在服务器端做,那就有点复杂了。
  • 理论上应该可以手动保存组件树,以序列化的形式携带它,然后在以后恢复它。我也许会尝试一下。

标签: jsf jsf-2.2


【解决方案1】:

我的同事刚刚提到可能有一种方法可以使用omnifaces 来实现这一点

到目前为止,有。 OmniFaces 中已经有了很多东西,除了一个小的缺失的关键部分。我只是 committed 一个 Hacks#getStateHelper() 到 2.3 SNAPSHOT,它应该将受保护的 UIComponent#getStateHelper() 方法公开。然后,它可以与自 1.0 以来已经在 OmniFaces 中的 EditableValueHolderStateHelper&lt;o:form&gt; and &lt;o:ignoreValidationFailed&gt; 一起使用,以便不管转换/验证错误如何都可以调用操作(就像“返回”按钮应该做的那样)。

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

@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());
    }

}

saveState 需要在表单组件的postValidate 事件期间被调用。 restoreState() 需要在表单组件的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 的所有优点。

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

【讨论】:

    【解决方案2】:

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

    相反,我必须在提交按钮后面的方法中编写验证逻辑。缺点是 JSF 并没有为你做这些工作。你必须自己做。好处是您可以更好地控制何时以及如何应用验证。

    【讨论】:

    • 您如何保存基于IntegerBigDecimal 的属性的无效值?这正是这个问题的全部意义所在。
    • 我没有专门处理这个问题,但一种方法是将表单绑定到字符串属性,然后在单击提交时尝试将字符串解析为目标数字属性。
    • 另一种方法是使用 Javascript 来防止在字段中输入非数字值。
    • JavaScript 可以被最终用户禁用/修改/欺骗。您的方法不够稳健,破坏了 JSF 和 BV 的所有优势。
    • 这不是使用 JavaScript 作为安全预防措施;它使用它来避免遇到 JSF 验证错误。如果用户想通过破解 JavaScript 来打自己的脚,那是他的问题。