In my JSF view I have a p:selectCheckboxMenu
where I want to perform some business logic via AJAX on the selected values.
For a simple change
event it works fine, but for a toggleSelect
event not. Inside my listener method I am retrieving the old selection, but I am expecting the new selection here.
See the following example:
@ViewScoped
@Named
public class RequestBean implements Serializable {
private List<String> list; // + getter/setter
@PostConstruct
private void init() {
list = new ArrayList<String>() {{
add("one"); add("two"); add("three");
}};
}
public void listener() {
System.out.println("Current content of \"list\":");
for(String s : list) {
System.out.println(s);
}
}
}
in JSF view:
<p:selectCheckboxMenu value="#{requestBean.list}" label="List">
<f:selectItem itemValue="one" itemLabel="one"/>
<f:selectItem itemValue="two" itemLabel="two"/>
<f:selectItem itemValue="three" itemLabel="three"/>
<p:ajax event="toggleSelect" listener="#{requestBean.listener}" />
<p:ajax event="change" listener="#{requestBean.listener}" />
</p:selectCheckboxMenu>
Now lets consider the following use-case: You are entering the view, "one" and "two" are selected. If I click the "select all" checkbox, the outcome is:
Info: Current content of "list":
Info: one
Info: two
But the expected outcome would look like this:
Info: Current content of "list":
Info: one
Info: two
Info: three
For the regular change event it works as expected. Here I am getting the new selection inside the listener. How may I fix it? Or what am I doing wrong?
GlassFish 4.1, running on Java 1.8.0_45
JSF 2.2.10 (Mojarra)
PrimeFaces 5.1
OmniFaces 1.8.1
This issue seems to be related with the listener being called too early. Doing some basic debugging, I've found that toggleSelect
invokes the listener method before updating the model values, while the change
event does it after modifying them. That's my current code:
RequestBean:
@ViewScoped
@ManagedBean
public class RequestBean implements Serializable {
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
System.out.println("Values set: " + list);
}
private List<String> list;
@PostConstruct
private void init() {
list = new ArrayList<String>() {
{
add("one");
add("two");
add("three");
}
};
}
public void listener() {
System.out.println("Listener called!");
}
}
page.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:comp="http://java.sun.com/jsf/composite/comp"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head />
<h:body>
<h:form>
<p:selectCheckboxMenu value="#{requestBean.list}" label="List">
<f:selectItem itemValue="one" itemLabel="one" />
<f:selectItem itemValue="two" itemLabel="two" />
<f:selectItem itemValue="three" itemLabel="three" />
<p:ajax event="toggleSelect" listener="#{requestBean.listener}" />
<p:ajax event="change" listener="#{requestBean.listener}" />
</p:selectCheckboxMenu>
</h:form>
</h:body>
</html>
And that's the trace for your current steps:
Values set: [one]
Listener called!
Values set: [one, two]
Listener called!
Listener called!
Values set: [one, two, three]
The last one is the toogle selection, as you can see the model is properly updated, but the listener is called before.
Let's play a little bit more with a custom PhaseListener
:
Entering RESTORE_VIEW 1
Entering APPLY_REQUEST_VALUES 2
Entering PROCESS_VALIDATIONS 3
Entering UPDATE_MODEL_VALUES 4
Values set: [one]
Entering INVOKE_APPLICATION 5
Listener called!
Entering RENDER_RESPONSE 6
Entering RESTORE_VIEW 1
Entering APPLY_REQUEST_VALUES 2
Entering PROCESS_VALIDATIONS 3
Entering UPDATE_MODEL_VALUES 4
Values set: [one, two]
Entering INVOKE_APPLICATION 5
Listener called!
Entering RENDER_RESPONSE 6
Entering RESTORE_VIEW 1
Entering APPLY_REQUEST_VALUES 2
Listener called!
Entering PROCESS_VALIDATIONS 3
Entering UPDATE_MODEL_VALUES 4
Values set: [one, two, three]
Entering INVOKE_APPLICATION 5
Entering RENDER_RESPONSE 6
As you can see, the model values are always set in the UPDATE_MODEL_VALUES
phase, while the change
event performs in INVOKE_APPLICATION
as it should, toggleSelect
listener perform in APPLY_REQUEST_VALUES
, which is before in the list.
This seems to be a Primefaces bug, which should be notified in their GitHub branch.
See also:
The listener gets triggered during the second phase of JSF lifecycle: Apply Request Values. The model (requestBean.list
) gets updated later, during the fourth phase, Update Model Values. That's why the listener sees the old value.
BalusC posted a general workaround in this answer, but it will cause an infinite loop in this case because of the way PrimeFaces' components wrap and enqueue events. So I suggest going the phase listener way.
View
<f:view beforePhase="#{requestBean.beforePhase}">
<h:form id="form">
<p:selectCheckboxMenu id="list_menu" value="#{requestBean.list}" label="List">
<f:selectItem itemValue="one" itemLabel="one" />
<f:selectItem itemValue="two" itemLabel="two" />
<f:selectItem itemValue="three" itemLabel="three" />
<!-- still need p:ajax toggleSelect to trigger the request -->
<p:ajax event="toggleSelect" />
<p:ajax event="change" listener="#{requestBean.listener}" />
</p:selectCheckboxMenu>
</h:form>
</f:view>
Bean
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
Map<String, String> params = FacesContext.getCurrentInstance()
.getExternalContext().getRequestParameterMap();
String eventName = params.get(
Constants.RequestParams.PARTIAL_BEHAVIOR_EVENT_PARAM);
String source = params.get("javax.faces.source");
if ("form-list_menu".equals(source) && "toggleSelect".equals(eventName)) {
listener();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With