We have a setting where we have different optional view params passed to JSF pages and subsequent view actions to be processed after the params have been set. A very simple example is shown below:
page.xhtml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<f:view>
<f:metadata>
<f:viewParam name="a" value="#{page.a}"/>
<f:viewAction action="#{page.populateA()}" if="#{not empty page.a}"/>
<f:viewParam name="b" value="#{page.b}"/>
<f:viewAction action="#{page.populateB()}"/>
</f:metadata>
<h:outputLabel value="#{page.message}"/>
</f:view>
</html>
Page
import javax.faces.view.ViewScoped;
import javax.inject.Named;
@ViewScoped
@Named
public class Page {
private String a;
private String b;
private String message;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getMessage() {
return message;
}
public void populateA() {
this.message = "Param a given: " + this.a;
}
public void populateB() {
if (this.b != null) {
this.message = "Param b given: " + this.b;
}
}
}
Now, the difference is that the processing of a does not work (page.xhtml?a=123), whilst the processing of b works like a charm (page.xhtml?b=123) - even though I thought I simply moved the null-check from Java to JSF. In respect of readability I would prefer to omit additional null-checks in Java and have the view param processing fully located in JSF, but how can I tweak the code to make the first scenario work?
EDIT According to the accepted answer of What is the purpose of rendered attribute on f:viewAction?, if works per se so I suspect a wrong execution order (first evaluate the action condition, then apply the values to the model).
The
<f:viewAction if>attribute is basically a renamed<h:xxx rendered>attribute (I'll leave in the middle whether that's more clear or not, it has at least a bug in autogenerated documentation as consequence; it listsrenderedinstead ofif) and has thus exactly the same lifecycle during among others the Apply Request Values phase as all other UI components. This phase calls theUIComponent#decode()of all UI components in the view. For theUIViewAction, the UI component class behind<f:viewAction>tag, thedecode()is described as follows:So, yes, you're indeed right as to "wrong execution order". During Apply Request Values phase it checks the
ifattribute and if it evaluatesfalse, then the decode won't take place and the action event won't be queued. See also line 609 ofUIViewActionsource code where it testsisRendered()insidedecode()and doesn't proceed iffalse. In your case, the#{page.a}, which you're checking in the<f:viewAction if>, is only available during Update Model Values phase, which is after the Apply Request Values phase.You'd like to test something else which is guaranteed to be available during Apply Request Values phase. The best candidate would be the actual request parameter itself. All "plain" request parameters are in EL scope available by the implicit EL object
#{param}which references aMap<String, String>with the parameter name as key.So, all in all, this should do:
Update to solve the unintuitive behavior of
<f:viewAction if>, JSF utility library OmniFaces will since version 2.2 offer an<o:viewAction>whoseifis only evaluated during Invoke Application phase, right before the action event is broadcasted. This trick was done by simply extending theUIViewActioncomponent and overriding itsbroadcast()andisRendered()as below:This allows you to use the tag more intuitively: