ADF 11g – implementing conditionally required input fields – by playing client side hide and seek

The requirement I was dealing with today in ADF 11g Rich Client Components was the following: we have an input field that is required under certain conditions. Only when one of this cluster of fields has a value, is it required. Otherwise it is optional. The use case was that the fields represent a detail record. There does not need to be a detail record (optional) but if there is one (one of the fields in the detailrecord has a value), then certain fields are mandatory.

The desire was to dynamically set the required-ness of the inputText – depending on whether one of the items in the detail record cluster has a value or not. Dynamically means that when something changes with one of the items, the conditionally required item is immediately refreshed to either required or optional.

ADF 11g - implementing conditionally required input fields - by playing client side hide and seek conditionallyRequired1

It turned out to be fairly easy to implement: the required attribute can be set using an EL expression that refers to a bean method. In the bean we can easily check the values of the other items and determine whether or not based on that assessment the inputText component is mandatory or not. By setting autoSubmit to true for all the items that determine the mandatoriness – and by adding the inputText as a partialTarget in the PPR request, we achieve the desired functionality. However …

In the JSF page:

 <af:inputText label="If this one has a value" id="it2"
               value="#{bean.value2}" autoSubmit="true" />
 <af:inputText label="Conditionally Required" id="it3"
               value="#{bean.value3}" required="#{bean.value3Required}"
               autoSubmit="true" binding="#{bean.value3Binding}" />

The binding attribute makes it easier to add inputText it3 as partialTarget in the bean.

The bean:

public class Bean {

    private String value2;
    private String value3;
    private UIComponent value3Binding;

    public Boolean getValue3Required() {
        return value2!=null && value2.length()>0;
    }

    private void refreshValue3() {
        AdfFacesContext.getCurrentInstance().addPartialTarget(value3Binding);
    }
    public void setValue2(String value2) {
        this.value2 = value2;
        refreshValue3(); // make the conditionally inputText component a partial target
    }

    public String getValue2() {
        return value2;
    }

    public void setValue3(String value3) {
        this.value3 = value3;
    }

    public String getValue3() {
        return value3;
    }

    public void setValue3Binding(UIComponent value3Binding) {
        this.value3Binding = value3Binding;
    }

    public UIComponent getValue3Binding() {
        return value3Binding;
    }

}

A more client oriented solution

Although this works fine, there is one snag: if the server round trip takes longer than desired, the user may have tabbed to the next field – the one that is about to become PPR-ed – and may have started typing in that field. When the PPR is then performed, the value that the user may have entered is overwritten with the original value known on the server. With short network round trips, this will hardly be an issue. However, when the bandwith is insufficient or the server is heavily loaded and the response comes in late, it may become a real issue. To illustrate the effect, I have added a deliberate two second delay in the bean that handles the PPR. Now I force the PPR response to be late to mimic what might happen in real life.

Suppose this scenario:

  • user enters a value in field 2
  • user tabs to field 3
  • PPR fires and will eventually make field 3 required
  • user starts to enter a value in field 3
  • PPR response comes and partially refreshes the page; part of this refresh is field 3 that is re-created based on whatever the server sent back to the browser, including the fact that now the field is required. However, the value already entered into the field by the user is overwritten by the last value remembered on the server.

End users may be less than happy with such behavior – although I want to stress that it will not happen like this very frequently: it takes a slows connection or server and a very fast user.

 

So can we come up with an implementation that does not frustrate the users like this in case of slow PPR response times? One that does not involve the server at all?

Well, unfortunately, the ADF 11g Rich Client Components do not allow us to invoke a setRequired() method. So we cannot change the required-ness of components after the page has loaded. So it seems that unfortunately no, we annot come up with a client side implementation. But wait, there is a trick…

What if we create two inputText components – one that is required and the other one that is not. Only one of them is visible at any moment in time. Depending on the values in the items that determine the requiredness, either the required half of the twins is shown or the un-required half. Whenever the value changes, a client listener fires and the mandatoriness is reassessed. Finally one other client listener – on the mandatory items – synchronizes the value to the unrequired twin. They both submit to the same bean property – that for now gets set twice with the same value (when the item is required) or with the dummy value for the required item and either a specifically set or no value for the unrequired item..

The salient elements in the page:

<af:resource type="javascript" source="/conditionalRequired.js" />
<af:inputText label="If this one has a value" id="it2"
              value="#{bean.value2}" >
    <af:clientListener method="toggleRequiredAndNonRequiredComponents"
                       type="valueChange"/>
</af:inputText>
<af:inputText label="Or this one has a value" id="it2b"
              value="#{bean.value2b}" >
    <af:clientListener method="toggleRequiredAndNonRequiredComponents"
                      type="valueChange"/>
</af:inputText>
<af:inputText label="Required Or Not" id="it4a"
              visible="#{bean.value3Required}" clientComponent="true"
              value="#{bean.value4}" required="true">
    <af:clientListener method="synchronizeWithCounterpart"
                       type="valueChange"/>
</af:inputText>
<af:inputText label="Required Or Not" id="it4b" clientComponent="true"
              visible="#{!bean.value3Required}"
              value="#{bean.value4}" required="false">
     <af:clientListener method="synchronizeWithCounterpart"
                        type="valueChange"/>
</af:inputText>

Small changes in the bean:

public class Bean {

     private static String dummyValue ="*&^%$tyerwy**";

     private String value2;
     private String value2b;
     private String value4;


     public Boolean getValue3Required() {
        return (value2!=null && value2.length()>0)
            || (value2b!=null && value2b.length()>0)
            ;
     }

     public void setValue2(String value2) {
         this.value2 = value2;
     }

     public String getValue2() {
         return value2;
     }

     public void setValue4(String value4) {
        if (!dummyValue.equals(value4))
        {
           this.value4 = value4;
        }
   }

   public String getValue4() {
     return value4;
   }

   public void setValue2b(String value2b) {
     this.value2b = value2b;
   }

   public String getValue2b() {
     return value2b;
   }
 }

And the Javascript:

 var dummyValue ='*&^%$tyerwy**';

 function toggleRequiredAndNonRequiredComponents(event) {
       c1 = AdfPage.PAGE.findComponent("it2");
       c2 = AdfPage.PAGE.findComponent("it2b");
       required = AdfPage.PAGE.findComponent("it4a");
       unrequired = AdfPage.PAGE.findComponent("it4b");

     if ((c1.getValue() != null && c1.getValue() != '')
         ||
         (c2.getValue() != null && c2.getValue() != '')
     ) { // make required
        required.setValue(unrequired.getValue());
        required.setVisible(true);
        unrequired.setVisible(false);
    }
    else { // make unrequired
        unrequired.setValue(required.getValue());
        unrequired.setVisible(true);
        required.setVisible(false);
        required.setValue(dummyValue);
    }
}

 function synchronizeWithCounterpart(event) {
     source = event.getSource();
     value = source.getValue();
     if (dummyValue!= value) {
       sourceId = source.getId();
       targetId = sourceId.substring(0, sourceId.length-1)+ (sourceId.substring(sourceId.length-1)=='a'?'b':'a');
       target = AdfPage.PAGE.findComponent(targetId);
       target.setValue(value);
    }
 }

The function toggleRequiredAndNonRequiredComponents(event) is triggered whenever the value of one of the items that determines the requiredness is changed. The function establishes whether or not the inputText it4 should be required not. Since we have two inputText items – a mandatory one and an optional one – it finds the either the required or the unrequired one and sets it visible – its counterpart is made invisble.

Function synchronizeWithCounterpart(event) takes care of synchronizing the values in the two counterpats. Whenever it4a is changed by the user, the value is reflected in it4b and vice versa. The code could be optimized to prevent it4b from updating it4a if the update was caused by it4a in the first place.

Resources

Simple JDeveloper 11g Application: ConditionallyRequiredFields.zip.

7 Comments

  1. Roopam October 13, 2011
  2. john October 5, 2011
  3. Johan September 13, 2011
  4. Amar January 29, 2011
  5. Joe February 15, 2010
  6. Lucas Jellema December 18, 2009
  7. Sten Vesterli December 17, 2009