2

I have a problem that validations from a composite's facet are being fired even when I do not render the composite.

I stripped the problem down to the following barebones code.

Here is the composite 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>

And here is the invocation:

<?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>

The combobox does NOT show on the screen (because it's not rendered), but why would I be getting a validation for something that's not rendered?

This is what I see when I click the SAVE button:

enter image description here

Stranger yet, is that I see THIS validation error even on other invocations of the composite that do NOT have that combobox.

I also noticed that if I do not include a unique ID on the <messages> tag, the message from one use of the composite will show up in other uses of the composite.

Is this a PrimeFaces or JSF bug, or am I missing something?

You might notice that I have a commented out <inputText> tag. It's worth mentioning that when I replace the <selectOneMenu> and replace it with the <inputText> I no longer see the problem.


I thought it might help to elucidate a bit on the larger problem I'm trying to solve.

I want to create something akin to a <p:layout> that has both fixed elements (for all uses of the composite) and non-fixed elements/panels which are passed in parametrically (for EACH use of the component).

Here is a screenshot where the items indicated in read are things that vary with each invocation of the composite. Everything else is always present in all invocations of the composite.

As you can see, the parameters are:

  1. A button panel (buttons vary depending on context)
  2. Some additional fields to add to the end of a form (which might contain validations
  3. An entire lower panel (which might contain validations)

Sample of the composite

It's worth mentioning that all these things are validated together (for "SAVE" buttons), so it's desirable to have the <form> tag be within the composite output (which includes the panels passed in as parameters).

DaBlick
  • 898
  • 9
  • 21

1 Answers1

4

This problem is two-fold.

First, the <cc:renderFacet> is never designed to work this way. It does not support the rendered attribute. That it somehow works is because the facet is internally re-interpreted as an UIPanel component and all attributes are (incorrectly) automatically inherited from the tag. You should not rely on that. The rendered attribute is incorrectly considered during render response, causing confusing behavior that it "works". This is technically a bug in the JSF implementation. The attributes are (correctly) not inherited during the postback, causing the trouble you observed. The components are still decoded and validated "in spite of" that they are not rendered.

Second, the <p:inputText> extends from UIInput which checks before validation if there's any submitted value. A submitted value of null is interpreted as complete absence of the input field in the form, so it's skipped. A submitted value of an empty string is interpeted as an empty value, so it's validated. The <p:selectOneMenu>, however, has overriden the standard UIInput behavior and considers null the same way as an empty string. Even when the submitted value is null (which means that the input field wasn't in the form at all), it's still being validated. This is technically a bug in PrimeFaces side.

Your intent is at least clear: conditionally render a facet. The <cc:xxx> tags are evaluated during Facelets compile time (which is a step before view build time), so conditionally building the <cc:renderFacet> using JSTL <c:if> will also not ever work.

Your best bet is redefining "render lower panel" as a composite attribute, and create a backing component to explicitly copy this attribute into the facet after it's being added to the view.

<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.
        }
    }

}

This has the additional benefit you can specify it from client on.

<my:entityDetailPanel ... renderLowerPanel="true" />
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    BalusC, thanks for that thoroughly excellent explanation and solution. I'm still puzzled by something. The tag w the invalid rendered attribute was introduced in my cutdown version. It was not in the original program. Remove the tag from the composite and u still get the validation. Furthermore, the original problem was that I was getting that validation error in EVERY use of the composite. Even in uses where the lowerPanel facet did NOT have the validation. Why is that happening and is there an easier way to avoid that other than your solution? Thanks. – DaBlick Feb 10 '16 at 14:30
  • 1
    [The facets are also processed during decode](http://docs.oracle.com/javaee/7/api/javax/faces/component/UIComponentBase.html#processDecodes-javax.faces.context.FacesContext-). Awkward in this construct if you actually never use the composite facet during encode, yes, but it is what it is. – BalusC Feb 10 '16 at 15:05
  • OK. What I want to do here doesn't seem out of the ordinary. Effectively I want to create something akin to a "layout" where I pass panels (with validating elements) in as parameters (quasi-facets). Sounds like I can't do that with a composite because the facets are processed during decode. Right? Is it possible for me to do this by writing a @FacesComponent or will I run into the same problem? Is that the best way to do it? I have to believe it can be done some way as what I'm trying to do isn't much different than what does (w layoutUnits acting like parameters?) – DaBlick Feb 10 '16 at 19:15
  • IMO you are overvaluing composite components. An example ideal use case of a composite component is a single reusable input component which is tied to a single (custom) bean property. E.g. a file upload and image cropper in one which is tied to a `com.example.Image` property ``. Or three dependent d/m/y dropdowns tied to a `(Local)Date` property ``. I think you just want a tagfile with define/insert. See also a.o. http://stackoverflow.com/q/6822000 – BalusC Feb 10 '16 at 19:27
  • OK. You are clearly correct that this is a template rather than a composite. I really appreciate your inputs and will donate if you've got that setup. – DaBlick Feb 10 '16 at 19:32