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.
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.
Hi Lucas,
I need to know how can I call different css classes for a page depending up differnt browsers.Like if i want to implement different set of css classes on a page for ie9.And i am using more of ADF tags in my web page.
hi lucas i want to do something like u did but i ant to do it with the buttons, i have a submit button and i have another one that cleans the fields but if i setRequired property to true in the fields the button that cleans the fields doesnt work and the message with the require fields keeps showing i just want it to show when i press submmit button
ps. thx
hi lucas, i’ve seen you’re solution. But to me it is not practical. I have pages with hundreds of required fields, are there other solutions to this problem?
I’ve a requirement wherein I need to refer to different details table based on a id ie the data for the details table can come either from Table A or Table B.Is there any best way to achieve this in ADF?
Lucas, do you think there is a way of achieving this purely with the ‘Validation Rules’ feature of ADF BC? If this could be implemented in the model I think it would save a lot of time, plus it would be portable to any page of you without the need for more backing beans.
Given the ‘Conditional Execution’ feature I’m sure there is a way to work this out 🙂
Hi Sten,
Duncan’s First Law however is one that is not attainable in real life. As a guiding principle to limit the volume of (unstructured, hard to maintain, difficult to upgrade, challenging to make multi-browser proof) JS code I subscribe to it. At the same time, to achieve scalable, responsive web applications, we need to not overdo on the PPR and the number of times we engage the server to step in. Latencies of 0.5 secs or more (we are talking internet application here with unpredictable network speeds) are simply too long for some PPR. So we try to limit the JS – but there are some very good use cases and validation is one of them.
By the way, my colleague on the project (Robert) has created an alternative solution that is even more concise. I will write about it in more detail shortly.
Lucas
Very clever!
I must point out, though, that it violates Duncan’s First Law: “Thou Shalt Not Write JavaScript” 😉