首页 > 解决方案 > 当用户想要覆盖时,JSF 表单验证跳过特定验证器以进行第二次提交

问题描述

我正在使用两个验证器验证用户输入的帐号,一个用于基本标准格式,另一个用于根据存储在数据库中的值验证帐号。有效帐号的数据库可能并不总是最新的,所以我想允许用户覆盖并提交他们输入的帐号,但前提是数据库验证失败。我总是想验证它的标准格式 8 个字符,没有空格。

    <h:form id="formId">
        <p:panelGrid>
            <p:row>
                <p:column>
                    <p:outputLabel value="Account : " for="acct" />
                </p:column>
                <p:column>
                    <p:selectOneMenu id="acct" value="#{bean.acct.acctNum}" effect="fold" editable="true" validator="acctLengthAndSpaceValidator" required="true" requiredMessage="Required">

                        <f:selectItems value="#{bean.mySavedAccounts}" var="acct"
                            itemLabel="#{acct.acctNumber} itemValue="#{acct.acctNumber}" />
                        <o:validator validatorId="accountDatabaseValidator" disabled="#{bean.skipDbValidation}" />
                    </p:selectOneMenu>
                </p:column>
                <p:column>
                    <p:messages for="acct" showDetail="true" skipDetailIfEqualsSummary="true" />
                </p:column>
            </p:row>
        </p:panelGrid>
        <br />

            
        <p:selectBooleanCheckbox rendered="#{facesContext.validationFailed}" value="#{bean.skipDbValidation}" itemLabel="I know this account is really valid, please skip validation and let me submit!">
            <p:ajax update="@this" listener="#{bean.testListener()}" />
        </p:selectBooleanCheckbox>

        <p:commandButton value="Submit" action="#{bean.submit()}" update="formId"/>
    </h:form>

该复选框在最初提交表单并出现任何验证失败后出现(我将弄清楚如何隔离到失败的 accountDatabaseValidator)。但是当我选择复选框并再次提交时,两个验证器仍然被触发。我添加了 ajax 侦听器进行调试,它没有触发,布尔值skipDbValidation仍然为 false。

也许我的方法在实现我对数据库进行验证的具体目标方面是不正确的,但随后让用户选择在初始失败后跳过数据库验证。

编辑 如果我rendered="#{facesContext.validationFailed}"从复选框中删除并让它一直可见,skipDbValidation如果复选框被选中,则布尔值将设置为 true,然后在后续提交时,skipDbValidation按预期忽略。但我不希望允许用户首先绕过可见的复选框。只有在验证失败后。

标签: validationjsf

解决方案


这不起作用的技术解释是rendered在处理表单提交期间重新评估该属性。此时,faces 上下文validationFailed不再存在(仅validationFailed在上一个请求期间),因此组件不再呈现,然后组件的提交值将不会被应用。这匹配 #6 的commandButton/commandLink/ajax action/listener method not invoked 或 input value not set/updated

通过在客户端而不是服务器端呈现它来解决您的工作是可以接受的。但我认为您只想在调用特定验证器时才显示它。至少有两种方法可以实现这一点:

  1. 检查UIInput#isValid()感兴趣的输入组件。您可以通过组件的binding属性将组件绑定到视图(而不是 bean!)来实现这一点,这样您就可以在同一视图的其他地方引用它。

     <p:selectOneMenu binding="#{acct}" ...>
         ...
     </p:selectOneMenu>
    
     ...
    
     <p:selectBooleanCheckbox styleClass="#{acct.valid ? 'ui-helper-hidden' : ''}" ...>
         ...
     </p:selectBooleanCheckbox>
    

    请注意,我借此机会重用了 PrimeFaces 提供的样式类。

  2. 或者,使验证器成为视图范围的 bean,并<o:validator binding>改为通过引用它。

     @Named
     @ViewScoped
     public class AccountDatabaseValidator implements Validator, Serializable {
    
         private boolean validationFailed;
    
         @Override
         public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
              // ...
    
              validationFailed = !isValid(value);
    
              if (validationFailed) {
                  throw new ValidatorException(createError("Invalid value"));
              }
         }
    
         public boolean isValidationFailed() {
             return validationFailed;
         }
     }
    
     <p:selectOneMenu ...>
         <o:validator binding="#{accountDatabaseValidator}" ... />
     </p:selectOneMenu>
    
     ...
    
     <p:selectBooleanCheckbox rendered="#{accountDatabaseValidator.validationFailed}" ...>
         ...
     </p:selectBooleanCheckbox>
    

推荐阅读