p:selectOneMenu 的自定义验证器使其无法渲染/无法渲染其他组件

Custom Validator for p:selectOneMenu make it not working rendering / not rendering other components

我有这个select:

<p:selectOneMenu  value="#{bean.val}">
    <f:selectItem itemValue="X" itemLabel="Select" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="wrapper" />
</p:selectOneMenu>

这是它更新的包装器:

<p:panel id="wrapper">
    <p:panel rendered="#{bean.val == 'A' or bean.val == 'B'}">
        <!-- insert your code here -->
    </p:panel> 
</p:panel>

包装器在 select 的外部,处于同一级别。

一开始,一切都还好。包装器已隐藏。

如果我 select 'A' 然后 'C',例如,包装器消失。 BUT,如果我再次 select 'A' 或 'B' 和 'X'(第一个“选择”,Select),包装器不会消失!

我在 bean.valsetter 中放置了一个 断点 。 setter 为所有选项调用 但是 对于 第一个 调试器中的值与 上一个一个!

但是!如果我 删除 验证器 ,它 有效 !

这是验证器:

@FacesValidator(value="requiredSelect")
public class RequiredSelect implements Validator {
    protected MessageUtil messageUtil = new MessageUtil();
    
    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String val = (String) value;
        
        if ("X".equals(val)) {
            FacesMessage msg = this.messageUtil.getDefaultValidationError();
            throw new ValidatorException(msg);
        }
    }
}

之前我使用了另一个验证器:

@FacesValidator(value="requiredString")
public class RequiredString implements Validator {
    protected MessageUtil messageUtil = new MessageUtil();
    
    @Override
    public void validate(
        FacesContext context, 
        UIComponent component, 
        Object value
    ) throws ValidatorException {
        String val = (String) value;
        
        if (val == null || val.trim().isEmpty()) {
            FacesMessage msg = this.messageUtil.getDefaultValidationError();
            throw new ValidatorException(msg);
        }
    }
}

但它 not 起作用,因为 空字符串 not 保存到菜单更改时的支持 bean。因此,例如,如果您 selected 'A' 然后返回 '' (Select) 并尝试提交表单,则 select 会发出错误信号, 但是selectreturns的'A'!所以伪造的 'X' 值。

我正在使用 Primefaces 3.4.1 和 Mojarra 2.1.7

你的问题有点 XY 问题。您正试图破解使用 X select 项目创建必需的 p:selectOneMenu 的方法。这需要您创建一个验证器。如果您只是简单地使 p:selectOneMenu 成为必需的,并且将使用 null 作为占位符的 itemValue,它就不需要处理 X,因此您不需要一个验证者。

选择null将导致验证失败。因此,一旦创建了 selection,您就不再需要渲染 null 占位符。通常人们会使用 p:selectOneMenuhideNoSelectionOption 属性。但这在 PrimeFaces 3.4.1 中不可用。由于您不想升级,因此可以通过为 SelectOneMenu.

创建自定义渲染器来添加此行为

创建一个新的 class,说 my.custom.MySelectOneMenuRenderer,并扩展 SelectOneMenuRenderer。在这里你想要 @OverrideencodeInput 方法是这样的:

protected SelectItem selectMeOption = new SelectItem(null, "Select");

@Override
protected void encodeInput(
    FacesContext context, SelectOneMenu menu, String clientId,
    List<SelectItem> selectItems, Object values, Object submittedValues,
    Converter converter
) throws IOException {
    String classes = menu.getStyleClass();
    Pattern pattern = Pattern.compile("\bnormalSelect\b");
    Matcher matcher = pattern.matcher(classes);
    boolean isNormal = matcher.find();
    Object value = menu.getValue();
    
    // If there's not a class "normalSelect" and no value is set, add the
    // null option as the first item
    if (!isNormal) {
        if (value == null) {
            selectItems.add(0, selectMeOption);
        }
        else {
            SelectItem firstOption = selectItems.get(0);
            
            if (selectMeOption.equals(firstOption)) {
                selectItems.remove(0);
            }
        }
    }
    
    super.encodeInput(
        context, menu, clientId, selectItems, values, submittedValues,
        converter
    );
}

将自定义渲染器添加到 faces-config.xml 中的 render-kit 部分,例如:

<render-kit>
  <renderer>
    <component-family>org.primefaces.component</component-family>
    <renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type>
    <renderer-class>my.custom.MySelectOneMenuRenderer</renderer-class>
  </renderer>
</render-kit>

查看示例:

<p:selectOneMenu value="#{bean.val}" layout="pageDirection">
    <f:selectItems value="#{bean.vals()}" />
    <p:ajax update="#{empty bean.val ? '@this' : ''}" />
</p:selectOneMenu>

有关工作示例,请参阅:https://github.com/jepsar/primefaces-test/blob/so_68457571/src/main/webapp/test.xhtml

另请参阅:

请在这里查看 JSF 生命周期示例: https://www.torsten-horn.de/img/JSF-Lifecycle.gif

并且您会注意到,如果您抛出“验证程序异常”,则会跳过“更新模型值”阶段。 (处理事件转发以呈现响应)。

因此,您认为在验证器中无效的值“X”永远不会发送到 bean,因此包装器不会消失。

你真正想要的是:

  • 定义一个合适的“NoSelect-Option”
  • 将字段设置为“必填”。

然后 - 提交后 - 必填字段将被视为空并在您需要的地方抛出验证器异常。

ps.: 绕过这个“问题”的一个巧妙方法是在 selectOneMenu 上使用 hideNoSelectionOption - 这样,表格以“请 select”,但用户一旦做出选择就不能切换回“请select”:

<p:selectOneMenu  value="#{bean.val}" hideNoSelectionOption="#{not empty bean.val}">
    <f:selectItem itemValue="#{null}" itemLabel="Select" noSelectionOption="true" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="@this,wrapper" process="@this" />
</p:selectOneMenu>

已更新,将 @this 添加到 p:ajax

PS:这不适用于没有 hideNoSelectionOption

的旧版本 Primefaces

好的,这是我的解决方案,感谢dognose's 第一部分:

  1. 将输入更改为:
<p:selectOneMenu value="#{bean.val}" required="true">
    <f:selectItem itemValue="" itemLabel="Select" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="wrapper" />
</p:selectOneMenu>

不需要itemValue="#{none}",因为默认情况下空字符串会自动转换为空字符串,通过jboss或primefaces,我不记得了。也不需要 noSelectionOption="true" (我不明白它的范围......即使我不放它验证也有效)

  1. 添加到包 my.package 文件 messages.properties,内容如下:
javax.faces.component.UIInput.REQUIRED = Error
javax.faces.component.UIInput.REQUIRED_detail = One or more fields are missing or wrong. Please check the form
  1. 然后我添加了一个 p:messages 像这样:
<p:messages id="messages" closable="true" showDetail="true" />
  1. 然后,我用删除了重复的消息。