Update model in the ValueChangeListener
After reading of the Understanding the JSF Immediate Attribute post, my friend
asked me whether there is any sense to use the Immediate attribute for
inputTexts. The answer is Yes, off course. In general we set the Immediate to
true when we need to validate the component and execute its value change events
before the common validation process, before the Process Validation phase. We
move up the validation and the value change events processing to the Apply
Request Values phase. In other words, we split our components into two parts:
front-line or Immediate inputs that are validated first at the Apply Request
Values and all others that are validated later at the usual Process Validation
phase. In this post I'm going to show one of use-cases when the Immediate
attribute for inputTexts could be useful.
Let's say we have two inputTexts:
Let's say we have two inputTexts:
<af:inputText label="Label Value" id="it1"
value="#{backing_Main.labelValue}" autoSubmit="true"
/>
<af:inputText label="#{backing_Main.labelValue}"
id="it2"
partialTriggers="it1"/>
The first one stores its value in some backing bean property (in the model), and the second input reads this property to render its label. The first input is autosubmit and it partially triggers the second input, so when we change value of the first input and press Tab, label of the second one is going to be changed immediately. At this point both inputs are not Immediate and everything works fine:
Let's change the required attribute
of the second input to true and we get validation error:
To resolve this issue let's set Immediate of
the first input to true and add a valueChangeListener. In the valueChangeListener we
need to manually update model for the first input because we are going to skip
all the subsequent phases (including Update Model phase)
except Render Response:
public
void labelListener(ValueChangeEvent valueChangeEvent)
{ UIComponent c
= valueChangeEvent.getComponent();
//This step actually invokes Update Model phase for this
//component
c.processUpdates(FacesContext.getCurrentInstance());
//Jump to the Render Response phase in order to avoid
//the validation
FacesContext.getCurrentInstance().renderResponse();
}
And it works fine again:
That's it!
Understanding the JSF Immediate Attribute
There is very common myth among ADF and JSF developers that
using Immediate attribute will avoid unnecessary validation in any case. It is
only partly true. The myth is growing up because of misunderstanding how the
Immediate attribute actually works. Using the attribute without understanding
of the mechanism very often lead to bugs and unpredictable behaviour of the
application. I hope, this post will be useful for developers to understand and
to remember when the Immediate attribute is a good option and when it is not.
I think, we should be aware of the following key points:
I think, we should be aware of the following key points:
·
Processing of
any components with Immediate attribute is moved up to the Apply
Request Values phase of the lifecycle. But there is a big difference
in processing of editableValueHolder (inputText) and actionSource
(commandButton) components.
·
For EditableValueHolder components
(like inputText) with Immediate attribute validation and valueChangeEvent
delivering are done in Apply Request Values phase instead of
usual Process Validation phase. This is the only change. The
lifecycle is not stopped, it is going on, there is no any
lifecycle phase skipping!
Restore View->Apply Request Values->Process
Validations->Update Model->Invoke App->Render Response
·
For ActionSource components
(like commandButton) with Immediate attribute, action event is delivered
to Apply Request Values phase instead of usual Invoke
Application phase. But!!! After that the lifecycle is jumping to the
end, to Render Response phase. Validation and Model Update
phases are skipped.
Restore View->Apply Request
Values->Render Response
In order to understand how these points actually affect the system's behaviour, I'm going to have some experiments with very simple form:
And the jspx for the form:
<af:form
id="f1">
<af:inputText label="Label 1" id="it1"
value="#{TestImmediateBean.firstInputValue}"
valueChangeListener="#{TestImmediateBean.firstInputListener}"
binding="#{TestImmediateBean.firstInputText}"/>
<af:inputText label="Label 2" id="it2"
value="#{TestImmediateBean.secondInputValue}"
valueChangeListener="#{TestImmediateBean.secondInputListener}"
binding="#{TestImmediateBean.secondInputText}"/>
<af:commandButton text="commandButton 1" id="cb1"
actionListener="#{TestImmediateBean.buttonActionListener}"/>
</af:form>
I've added log messages to the valueChangeListeners, setters, and actionListener in order to follow the request process:
private void
printCurrenPhaseID() {
FacesContext
fctx = FacesContext.getCurrentInstance();
Map requestMap
= fctx.getExternalContext().getRequestMap();
PhaseId
currentPhase=(PhaseId)requestMap.get("oracle.adfinternal.view.faces.lifecycle.CURRENT_PHASE_ID");
System.out.println("currentPhase
= "+currentPhase);
}
public void
firstInputListener(ValueChangeEvent valueChangeEvent) {
System.out.println("firstInputListener {");
printCurrenPhaseID();
System.out.println("NewValue = " + valueChangeEvent.getNewValue());
System.out.println("}");
}
public void
secondInputListener(ValueChangeEvent valueChangeEvent) {
System.out.println("secondInputListener {");
printCurrenPhaseID();
System.out.println("NewValue = " + valueChangeEvent.getNewValue());
System.out.println("}");
}
public void
setFirstInputValue(String firstInputValue) {
this.firstInputValue = firstInputValue;
System.out.println("firstInputValue setter {");
printCurrenPhaseID();
System.out.println("}");
}
public void
setSecondInputValue(String secondInputValue) {
this.secondInputValue = secondInputValue;
System.out.println("secondInputValue setter {");
printCurrenPhaseID();
System.out.println("}");
}
public void
buttonActionListener(ActionEvent actionEvent) {
System.out.println("actionEventListener {");
printCurrenPhaseID();
System.out.println("firstComponentValue = " +
firstInputText.getValue());
System.out.println("secondComponentValue = " +
secondInputText.getValue());
System.out.println("firstModelValue = " + getFirstInputValue());
System.out.println("secondModelValue = " +
getSecondInputValue());
System.out.println("}");
}
So, Experiment #1. Standard situation, nobody has Immediate attribute:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
false
|
false
|
false
|
SecondInput |
false
|
false
|
false
|
CommandButton |
false
|
X
|
X
|
Input values and click the button:
In the system log we can see the following:
firstInputListener {
currentPhase = PROCESS_VALIDATIONS 3
NewValue = 1
}
secondInputListener {
currentPhase = PROCESS_VALIDATIONS 3
NewValue = 2
}
firstInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
secondInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
actionEventListener {
currentPhase = INVOKE_APPLICATION 5
firstComponentValue = 1
secondComponentValue = 2
firstModelValue = 1
secondComponentValue = 2
}
Everything is good. ValueChangelisteners are delivered to the Process Validation phase, setters are fired at the Update Model phase and actionListener is delivered to the Invoke Application phase.
Experiment #2. FirstInput has Immediate attribute:
firstInputListener {
currentPhase = PROCESS_VALIDATIONS 3
NewValue = 1
}
secondInputListener {
currentPhase = PROCESS_VALIDATIONS 3
NewValue = 2
}
firstInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
secondInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
actionEventListener {
currentPhase = INVOKE_APPLICATION 5
firstComponentValue = 1
secondComponentValue = 2
firstModelValue = 1
secondComponentValue = 2
}
Everything is good. ValueChangelisteners are delivered to the Process Validation phase, setters are fired at the Update Model phase and actionListener is delivered to the Invoke Application phase.
Experiment #2. FirstInput has Immediate attribute:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
true
|
false
|
false
|
SecondInput |
false
|
false
|
false
|
CommandButton |
false
|
X
|
X
|
And the log:
firstInputListener {
currentPhase = APPLY_REQUEST_VALUES 2
NewValue = 1
}
secondInputListener {
currentPhase = PROCESS_VALIDATIONS 3
NewValue = 2
}
firstInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
secondInputValue setter {
currentPhase = UPDATE_MODEL_VALUES 4
}
actionEventListener {
currentPhase = INVOKE_APPLICATION 5
firstComponentValue = 1
secondComponentValue = 2
firstModelValue = 1
secondComponentValue = 2
}
Note, that valueChangeListener for the FirstInput was delivered to Apply Request Value phase (because of Immediate). All other phases don't have any changes.
Experiment #3. CommandButton has Immediate attribute:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
false
|
false
|
false
|
SecondInput |
false
|
false
|
false
|
CommandButton |
true
|
X
|
X
|
And the Log:
actionEventListener {
currentPhase = APPLY_REQUEST_VALUES 2
firstComponentValue = null
secondComponentValue = null
firstModelValue = null
secondComponentValue = null
}
ValueChangeListeners and setters are not fired at all! Component values and Model values are null. They should be updated in Process Validations and Update Model phases, but they were skipped (key point #3). Very predictable result.
Experiment #4. FirstInput and CommandButton have Immediate attribute:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
true
|
false
|
false
|
SecondInput |
false
|
false
|
false
|
CommandButton |
true
|
X
|
X
|
And the Log:
firstInputListener {
currentPhase = APPLY_REQUEST_VALUES 2
NewValue = 1
}
actionEventListener {
currentPhase = APPLY_REQUEST_VALUES 2
firstComponentValue = 1
secondComponentValue = null
firstModelValue = null
secondComponentValue = null
}
This is very important! Process Validations and Update Model phases are skipped as in the previous experiment because commandButton has Immediate , but valueChangeListener for FirstInput is fired because it has Immediate as well (key point #2). And value of the component (RichInputText) is updated too. So, when you click the "Immediate" button, valueChangeListeners for every "Immediate" input fire as well. Be aware of that! I've recently fixed some bug connected with that. And vice-versa, if you need in an actionListener of the "Immediate" button any components' values, set their Immediate to true.
What do you think will happen if Required of the FirstInput is set true?!
Experiment #5. FirstInput and SecondInput are required. FirstInput and CommandButton have Immediate attribute:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
true
|
false
|
true
|
SecondInput |
false
|
false
|
true
|
CommandButton |
true
|
X
|
X
|
Let's leave inputs empty and click the button. In this case we don't have any log, we have the following:
Note! The button has
immediate attribute set to true, but validation is failed. Because validation
process for the FirstInput is moved up to Apply Request Values (Key Point #2).
The myth is busted! On the other hand, the SecondInput is required as well, but
there is no any problem with its validation. The SecondInput is not Immediate.
It was going to be validated at Process Validation phase, but the phase was
skipped because of Immediate button (Key Point #3). So, the myth is partly true
:).
Experiment #6. FirstInput is autoSubmit. SecondInput is a partial target for the FirstInput:
Experiment #6. FirstInput is autoSubmit. SecondInput is a partial target for the FirstInput:
Immediate |
AutoSubmit |
Required |
|
FirstInput |
true
|
true
|
true
|
SecondInput |
false
|
false
|
true
|
CommandButton |
true
|
X
|
X
|
Let's input something into the FirstInput and press Tab. We have validation exception:
Note! The FirstInput is Immediate, but we
still have the exception. The myth is busted again. Processing of
editableValueHolder components does not skip Process Validation
phase. It doesn't skip any phase at all. (Key Point #2)
How to avoid this exception? We have to move up the lifecycle to the end manually. In the valueChangeListener we add the following:
How to avoid this exception? We have to move up the lifecycle to the end manually. In the valueChangeListener we add the following:
public void
firstInputListener(ValueChangeEvent valueChangeEvent) {
System.out.println("firstInputListener {");
printCurrenPhaseID();
System.out.println("NewValue = " + valueChangeEvent.getNewValue());
System.out.println("}");
//Jump to the Render Response phase
FacesContext.getCurrentInstance().renderResponse();
}
And the exception's gone.
So, in order to use the Immediate attribute properly, remember three key points mentioned at the beginning of the post.
No comments:
Post a Comment