29 Nov 2013

What you may need to know about ADF Faces selection components and PPR

In this post I'm going to uncover a pitfall to watch for when using ADF Faces selection components such as af:selectBooleanCheckbox, af:selectOneChoice, af:selectOneRadio, af:selectManyCheckbox, af:selectBooleanRadio, af:selectManyChoice, etc. 

Let's consider a very simple example:
<af:inputText label="Label 1" id="it1"
              autoSubmit="true"                            
              />
<af:panelGroupLayout id="pgl1">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1"
                            />
</af:panelGroupLayout>
<af:outputText value="#{TestBean.someBooleanValue}" 
               id="ot1" partialTriggers="it1"/>
 
The default value of TestBean.someBooleanValue is true, so it's going to look like this:

SelectBooleanCheckbox and OutputText are partial targets of the InputText, though the InputText actually does nothing except submitting a request to the server.
For some reason we want to hide the PanelGroupLayout. Let's set the rendered attribute of the PanelGroupLayout to false:
<af:inputText label="Label 1" id="it1"
              autoSubmit="true"                            
              />
<af:panelGroupLayout id="pgl1" rendered="false">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            />
</af:panelGroupLayout>
<af:outputText value="#{TestBean.someBooleanValue}" 
               id="ot1" partialTriggers="it1"/>
and refresh the page:

Let's enter a value of the InputText and press Tab:

We can see that TestBean.someBooleanValue got false. Why?
Since SelectBooleanCheckbox and OutputText are partial targets of the InputText, the framework is going to process these components during the request along with the InputText.
Very roughly the framework does the following for each component participating in a request:
 if (component.isRendered() && component.getSubmittedValue()!=null) 
  processComponent(component);
else
  skipComponent(component);
   

  
The root of the evil is in the getSubmittedValue() method. Since selectBooleanCheckbox is not visible (it is even not rendered because it is laying on a panel which is not rendered), there is no any submitted value for it. It just can't be there. But getSubmittedValue() never returns null for selection components. In case of  selectBooleanCheckbox it returns false instead of null. If it was an inputText component, it would return null for sure.
The second problem is that the framework checks whether the component is rendered or not, but it doesn't pay attention to its parents.
So, in our case, we have a SelectBooleanCheckbox with "false" submitted value participating in the request. For sure, it will set its model value to false.

How to avoid this pitfall?
We can either set rendered attribute of the selectBooleanCheckbox to false:
<af:panelGroupLayout id="pgl1" rendered="false">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            rendered="false"
                            />
</af:panelGroupLayout>


or set the PanelGroupLayout as a partial target of the InputText:
<af:panelGroupLayout id="pgl1" rendered="false"
                     partialTriggers="it1">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            />
</af:panelGroupLayout>
That's it!

No comments:

Post a Comment

Post Comment