PrimeFaces selectCheckboxMenu: old values ​​in toggleSelect actionListener

In my JSF view, I have p:selectCheckboxMenu where I want to execute some business logic through AJAX on the selected values.

It works fine for a simple change event, but not for a toggleSelect event. In my listener method, I take the old choice, but I'm waiting for a new choice 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 the 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 consider the following use case: you enter a view, one and two are selected. If I click on the Select All checkbox, the result will be as follows:

 Info: Current content of "list": Info: one Info: two 

But the expected result will look like this:

 Info: Current content of "list": Info: one Info: two Info: three 

For a regular change event, it works as expected. Here I get a new choice inside the listener. How can i fix this? 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

+6
source share
6 answers

This problem seems to be due to the fact that the listener is called too early. While doing some basic debugging, I found that toggleSelect calls the listener method before updating the model values, and the change event does this after modifying them. What is 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 what a trace for your current steps:

 Values set: [one] Listener called! Values set: [one, two] Listener called! Listener called! Values set: [one, two, three] 

The final option is Google’s choice, as you can see that the model is updated correctly, but the listener is called earlier.

Let's play a little 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, and the change event is executed in INVOKE_APPLICATION , as you would expect, the toggleSelect listener executes in APPLY_REQUEST_VALUES , which is up to the list.

This seems to be a Primefaces bug that should be notified in their GitHub branch .

See also:

+4
source

The listener starts during the second phase of the JSF life cycle: Apply Request Values. The model ( requestBean.list ) is updated later, in the fourth stage, updates the model values. Therefore, the listener sees the old meaning.

BalusC posted a general workaround in this answer , but in this case it will cause an infinite loop due to the way PrimeFaces components complete and complete events. Therefore, I suggest moving on to the phase listener.

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(); } } } 
+3
source

I came across another workaround: basically you need to fill in the shortcut text inside the installer for the value. For instance:

View

 <p:selectCheckboxMenu value="#{bean.selectedValues}" label="#{bean.selectedValuesDisplayString}" widgetVar="checkboxMenuWget"> <f:selectItems value="#{bean.allValues}"/> <p:ajax event="change" listener="#{bean.updateDisplayString()}" update="@widgetVar(checkboxMenuWget)" /> <p:ajax event="toggleSelect" update="@widgetVar(checkboxMenuWget)" /> </p:selectCheckboxMenu> 

Bean

 // Properties private List<String> allValues; private List<String> selectedValues; private String selectedValuesDisplayString; // Getters and Setters public void setSelectedValues(List<String> selectedValues) { this.selectedValues = selectedValues; updateDisplayString(); } // UI Code public void updateDisplayString() { this.selectedValuesDisplayString = // code to generate display string... } 
0
source

I also ran into this problem. In PrimeFaces 5.2, it has not yet been fixed. My solution how to fix:

add to faces-config.xml

 <component> <component-type>org.primefaces.component.SelectCheckboxMenu</component-type> <component-class>yourpackage.SelectCheckboxMenu</component-class> </component> 

create this class:

 public class SelectCheckboxMenu extends org.primefaces.component.selectcheckboxmenu.SelectCheckboxMenu { @Override public void queueEvent(FacesEvent event) { FacesContext context = getFacesContext(); String eventName = context.getExternalContext().getRequestParameterMap().get(Constants.RequestParams.PARTIAL_BEHAVIOR_EVENT_PARAM); if(event instanceof AjaxBehaviorEvent && eventName.equals("toggleSelect")) { Map<String,String> params = context.getExternalContext().getRequestParameterMap(); String clientId = this.getClientId(context); boolean checked = Boolean.valueOf(params.get(clientId + "_checked")); // changed code ToggleSelectEvent toggleSelectEvent = new ToggleSelectEvent(this, ((AjaxBehaviorEvent) event).getBehavior(), checked); toggleSelectEvent.setPhaseId(event.getPhaseId()); getParent().queueEvent(toggleSelectEvent); // end } else { super.queueEvent(event); } } } 
0
source

You can use the remote command for the listener as shown below.

 <p:remoteCommand name="test" id="test" actionListener"#{requestBean.listener}" update="@form" process="@form"></p:remoteCommand> 

Call. Following the above methods, select toggleselect

  <p:ajax event="toggleSelect" oncomplete="test();" /> 
0
source

Faced with the same problem, after some searching, I found that you can bind your update logic to the onchange attribute. How:

 <p:selectCheckboxMenu value="#{requestBean.list}" label="List" onchange="#{requestBean.listener()}"> <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> 

Consider: onchange="#{requestBean.listener()}" you need to call the listener using curly braces, otherwise you will get an exception.

I don’t know which version of PF you are using, but I found a solution in this link . (PrimeFaces 5.3)

-1
source

Source: https://habr.com/ru/post/985512/


All Articles