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!

17 Nov 2013

Validating dates with af:validateDateTimeRange validator

Sometimes we need to validate that the date entered in an af:inputDate component is within some range. ADF Faces provides a very convenient approach for that - af:validateDateTimeRange. It is very easy to use and it could look like this in a jspx code:
<af:inputDate value="#{dateValue}">
   <af:validateDateTimeRange minimum="#{minimumValue}"
        maximum="#{maximumValue}"
        messageDetailNotInRange="The date value {1} is not in the range {2} - {3}"
        /> 
</af:inputDate>
But there is some pitfall to be aware.

Let's say we've got some ViewObject with three oracle.jbo.domain.Date attributes - ValueDate, MaturityDate and PaymentDate:


We put correspondent inputDate components on a page and set up autoSubmit attribute for ValueDate and MaturityDate:

 
We need to keep the value of PaymentDate within the range between ValueDate and MaturityDate. So, w're going to add a validator to the PaymentDate:

<af:inputDate value="#{bindings.PaymentDate.inputValue}"
              label="#{bindings.PaymentDate.hints.label}"
              required="#{bindings.PaymentDate.hints.mandatory}"
              columns="#{bindings.PaymentDate.hints.displayWidth}"
              shortDesc="#{bindings.PaymentDate.hints.tooltip}"
              id="id3">
  <f:validator binding="#{bindings.PaymentDate.validator}"/>
  <af:convertDateTime pattern="#{bindings.PaymentDate.format}"/>
  
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.Maturitydate.inputValue.value}"
                            />
  
</af:inputDate>


And let's test it now:

The error message is absolutely correct since PaymentDate is less than ValueDate.



The value of PaymentDate is equal to ValueDate and it is within the range ValueDate-MaturityDate. So there is no any validation exception.



Ooops!  The value of PaymentDate is equal to MaturityDate and it is within the range ValueDate-MaturityDate. But the validation exception is fired.
We can figure out the reason of this strange behavior in the specification of the maximum attribute of the af:validateDateTimeRange:

"... When binding to Date objects, it is advised to create the Date object with maximum value for any date-time components that aren't displayed (usually hours, minutes, seconds, milliseconds) to allow the largest range of values to be accepted."

So, in order to get it working correctly, it is advised to specify in the maximum attribute of the validateDateTimeRange something like this: "5/24/2012 23:59:59.999".

Let's give it what it wants. In our ViewObject we created a transient oracle.jbo.domain.Date attribute LimitMaturityDate with the following value expression:

 The expression actually adds a day (24 hours) to the Maturitydate and subtracts a milisecond, so we're going to get "Maturitydate 23:59:59.999". The new java.sql.Timestamp object is going to be converted into the oracle.jbo.domain.Date by the framework. And let's use this transient attribute as a maximum for the af:validateDateTimeRange:
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.LimitMaturityDate.inputValue.value}"
                            />


And if we test it again:


It will work fine.


That's it!