为什么在未呈现方面时从我的 JSF 复合方面进行验证

Why are validations from my JSF composite facet being done when the facet is not rendered

我有一个问题,即即使我不渲染复合材料,复合材料方面的验证也会被触发。

我将问题简化为以下准系统代码。

这里是合成entityDetailPanel:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:h="http://java.sun.com/jsf/html"
            xmlns:composite="http://java.sun.com/jsf/composite"
            xmlns:p="http://primefaces.org/ui"
            xmlns:common="http://java.sun.com/jsf/composite/common">

<composite:interface>
    <composite:attribute name="prefix" required="true" />
    <composite:facet name="lowerPanel"/>
</composite:interface>

<composite:implementation>

    <h:form id="#{cc.attrs.prefix}entityDetailForm2" 
            styleClass="#{cc.attrs.prefix}EntityDetailPanelForm #{cc.attrs.prefix}Listener" >

        <p:messages id="#{cc.attrs.prefix}messages" autoUpdate="true" closable="true"/>

        <p:commandButton 
            value="SAVE" 
            update="@(.#{cc.attrs.prefix}Listener), @(.#{cc.attrs.prefix}EntityDetailPanelForm}"/>

        <composite:renderFacet name="lowerPanel" rendered="false"/>
    </h:form>

</composite:implementation>
</ui:composition>

这里是调用:

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:f="http://java.sun.com/jsf/core"
            xmlns:p="http://primefaces.org/ui"
            xmlns:common="http://xmlns.jcp.org/jsf/composite/common">

    <common:entityDetailPanel id="foo" prefix="Instruments">

        <f:facet name="lowerPanel">
            <!--  <p:inputText id="assetClassPrompt" required="true"  requiredMessage="Why do we get this message?"/>-->

            <p:selectOneMenu id="assetClassPrompt" required="true"  requiredMessage="Why do we get this message?"
                             value="#{instrumentController.selectedData.assetClass}">
                 <f:selectItem itemLabel="foo" itemValue="foo"/>
                 <f:selectItem itemLabel="bar" itemValue="bar"/>
            </p:selectOneMenu>
        </f:facet>
    </common:entityDetailPanel>

</ui:composition>

组合框未显示在屏幕上(因为它未呈现),但为什么我会得到未呈现内容的验证?

这是我单击“保存”按钮时看到的内容:

更奇怪的是,即使在没有该组合框的组合的其他调用中,我也看到了这个验证错误。

我还注意到,如果我没有在 <messages> 标签上包含唯一 ID,来自组合的一次使用的消息将显示在组合的其他使用中。

这是 PrimeFaces 或 JSF 错误,还是我遗漏了什么?

您可能会注意到我注释掉了 <inputText> 标记。值得一提的是,当我替换 <selectOneMenu> 并将其替换为 <inputText> 时,我不再看到问题。


我认为这可能有助于阐明我试图解决的更大问题。

我想创建类似于 <p:layout> 的东西,它既有固定元素(用于复合材料的所有用途),也有非固定元素 elements/panels,它们以参数方式传入(用于 EACH 使用组件)。

这是一个屏幕截图,其中 read 中指示的项目是随每次组合调用而变化的内容。其他所有内容始终存在于组合的所有调用中。

可以看到,参数是:

  1. 按钮面板(按钮因上下文而异)
  2. 要添加到表单末尾的一些附加字段(可能包含验证
  3. 整个下面板(可能包含验证)

值得一提的是,所有这些东西都是一起验证的(对于 "SAVE" 按钮),因此希望 <form> 标签位于复合输出中(包括传入的面板)参数)。

这个问题有两个方面。

首先,<cc:renderFacet> 从未设计为以这种方式工作。它 does not 支持 rendered 属性。它以某种方式起作用是因为该方面在内部被重新解释为 UIPanel 组件,并且所有属性都(错误地)自动从标签继承。你不应该依赖它。 rendered 属性在渲染响应期间被错误地考虑,导致它 "works" 的混淆行为。从技术上讲,这是 JSF 实现中的一个错误。这些属性(正确地)在回发期间继承,导致您观察到的问题。组件仍在解码和验证 "in spite of" 它们未被渲染。

其次,<p:inputText> 扩展自 UIInput,它在验证之前检查是否有 any 提交的值。提交的值 null 被解释为表单中完全没有输入字段,因此被跳过。空字符串的提交值被插入为空值,因此它被验证。然而,<p:selectOneMenu> 已经覆盖了标准的 UIInput 行为,并将 null 视为空字符串。即使提交的值为 null(这意味着输入字段根本不在表单中),它仍在被验证。这在技术上是 PrimeFaces 方面的一个错误。

你的意图至少很明确:有条件地渲染一个方面。 <cc:xxx> 标记在 Facelets 编译期间(这是视图构建时间之前的一个步骤)进行评估,因此使用 JSTL <c:if> 有条件地构建 <cc:renderFacet> 也永远不会起作用。

你最好的选择是将 "render lower panel" 重新定义为复合属性,并创建一个支持组件以在将此属性添加到视图后将其显式复制到构面中。

<cc:interface componentType="entityDetailPanelComposite">
    ...
    <cc:facet name="lowerPanel" />
    <cc:attribute name="renderLowerPanel" type="java.lang.Boolean" default="false" />
</cc:interface>
<cc:implementation>
    <f:event type="postAddToView" listener="#{cc.init}" />
    ...
    <cc:renderFacet name="lowerPanel" />
    ...
</cc:implementation>

@FacesComponent("entityDetailPanelComposite")
public class EntityDetailPanelComposite extends UINamingContainer {

    public void init() {
        UIComponent lowerPanel = getFacets().get("lowerPanel");
        ValueExpression renderLowerPanel = getValueExpression("renderLowerPanel");

        if (renderLowerPanel != null) {
            lowerPanel.setValueExpression("rendered", renderLowerPanel); // It's an EL expression.
        } else {
            lowerPanel.getAttributes().put("rendered", getAttributes().get("renderLowerPanel")); // It's a literal value, or the default value.
        }
    }

}

这有额外的好处,您可以在客户端指定它。

<my:entityDetailPanel ... renderLowerPanel="true" />