PrimeFaces selectCheckboxMenu:toggleSelect actionListener 中的旧值
PrimeFaces selectCheckboxMenu: Old values in toggleSelect actionListener
在我的 JSF 视图中,我有一个 p:selectCheckboxMenu
,我想在其中通过 AJAX 对所选值执行一些业务逻辑。
对于简单的 change
事件,它工作正常,但对于 toggleSelect
事件则不然。在我的侦听器方法中,我正在检索旧的选择,但我期待这里有新的选择。
参见以下示例:
@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);
}
}
}
在 JSF 视图中:
<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>
现在让我们考虑以下用例:您正在进入视图,选择了 "one" 和 "two"。如果我点击 "select all" 复选框,结果是:
Info: Current content of "list":
Info: one
Info: two
但预期的结果是这样的:
Info: Current content of "list":
Info: one
Info: two
Info: three
对于常规更改事件,它按预期工作。在这里,我在侦听器中获取新选择。我该如何解决?或者我做错了什么?
GlassFish 4.1, running on Java 1.8.0_45
JSF 2.2.10 (Mojarra)
PrimeFaces 5.1
OmniFaces 1.8.1
这个问题似乎与侦听器被过早调用有关。进行一些基本调试后,我发现 toggleSelect
在更新模型值之前调用侦听器方法,而 change
事件在修改它们之后调用。这是我当前的代码:
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>
这是您当前步数的轨迹:
Values set: [one]
Listener called!
Values set: [one, two]
Listener called!
Listener called!
Values set: [one, two, three]
最后一个是toogle选择,你可以看到模型已经正确更新,但是之前调用了监听器。
让我们再玩一点自定义 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
如您所见,模型值始终在 UPDATE_MODEL_VALUES
阶段设置,而 change
事件在 INVOKE_APPLICATION
中正常执行,toggleSelect
侦听器在 APPLY_REQUEST_VALUES
中执行,它在列表之前。
这似乎是 Primefaces 的一个错误,应该在 their GitHub branch 中通知。
另请参阅:
- How to implement a PhaseListener which runs at end of lifecycle?
侦听器在 JSF 生命周期的第二阶段被触发:应用请求值。模型 (requestBean.list
) 稍后会在第四阶段更新,即更新模型值。这就是侦听器看到旧值的原因。
BalusC 发布了通用解决方法 in this answer,但由于 PrimeFaces 的组件包装和排队事件的方式,在这种情况下会导致无限循环。所以我建议采用相位监听器的方式。
查看
<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>
豆子
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();
}
}
}
我偶然发现了另一个解决方法:基本上您需要从 setter 中填充标签文本作为值。例如:
查看
<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>
豆子
// 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...
}
我也遇到了这个问题。它在 PrimeFaces 5.2 中仍未修复。我的解决方案是如何修复它:
添加到 faces-config。xml
<component>
<component-type>org.primefaces.component.SelectCheckboxMenu</component-type>
<component-class>yourpackage.SelectCheckboxMenu</component-class>
</component>
创建这个 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);
}
}
}
您可以像下面这样对监听器使用远程命令。
<p:remoteCommand name="test" id="test" actionListener"#{requestBean.listener}" update="@form" process="@form"></p:remoteCommand>
在 toggleselect 上调用以下代码方式
<p:ajax event="toggleSelect" oncomplete="test();" />
面临同样的问题,经过一段时间的搜索后我发现您可以将更新逻辑绑定到 onchange
属性。喜欢:
<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>
考虑:onchange="#{requestBean.listener()}"
你必须使用大括号调用监听器,否则你会得到一个异常。
不知道你用的是哪个版本的PF,不过我在this link中找到了解决办法。 (PrimeFaces 5.3)
在我的 JSF 视图中,我有一个 p:selectCheckboxMenu
,我想在其中通过 AJAX 对所选值执行一些业务逻辑。
对于简单的 change
事件,它工作正常,但对于 toggleSelect
事件则不然。在我的侦听器方法中,我正在检索旧的选择,但我期待这里有新的选择。
参见以下示例:
@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);
}
}
}
在 JSF 视图中:
<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>
现在让我们考虑以下用例:您正在进入视图,选择了 "one" 和 "two"。如果我点击 "select all" 复选框,结果是:
Info: Current content of "list":
Info: one
Info: two
但预期的结果是这样的:
Info: Current content of "list":
Info: one
Info: two
Info: three
对于常规更改事件,它按预期工作。在这里,我在侦听器中获取新选择。我该如何解决?或者我做错了什么?
GlassFish 4.1, running on Java 1.8.0_45
JSF 2.2.10 (Mojarra)
PrimeFaces 5.1
OmniFaces 1.8.1
这个问题似乎与侦听器被过早调用有关。进行一些基本调试后,我发现 toggleSelect
在更新模型值之前调用侦听器方法,而 change
事件在修改它们之后调用。这是我当前的代码:
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>
这是您当前步数的轨迹:
Values set: [one]
Listener called!
Values set: [one, two]
Listener called!
Listener called!
Values set: [one, two, three]
最后一个是toogle选择,你可以看到模型已经正确更新,但是之前调用了监听器。
让我们再玩一点自定义 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
如您所见,模型值始终在 UPDATE_MODEL_VALUES
阶段设置,而 change
事件在 INVOKE_APPLICATION
中正常执行,toggleSelect
侦听器在 APPLY_REQUEST_VALUES
中执行,它在列表之前。
这似乎是 Primefaces 的一个错误,应该在 their GitHub branch 中通知。
另请参阅:
- How to implement a PhaseListener which runs at end of lifecycle?
侦听器在 JSF 生命周期的第二阶段被触发:应用请求值。模型 (requestBean.list
) 稍后会在第四阶段更新,即更新模型值。这就是侦听器看到旧值的原因。
BalusC 发布了通用解决方法 in this answer,但由于 PrimeFaces 的组件包装和排队事件的方式,在这种情况下会导致无限循环。所以我建议采用相位监听器的方式。
查看
<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>
豆子
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();
}
}
}
我偶然发现了另一个解决方法:基本上您需要从 setter 中填充标签文本作为值。例如:
查看
<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>
豆子
// 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...
}
我也遇到了这个问题。它在 PrimeFaces 5.2 中仍未修复。我的解决方案是如何修复它:
添加到 faces-config。xml
<component>
<component-type>org.primefaces.component.SelectCheckboxMenu</component-type>
<component-class>yourpackage.SelectCheckboxMenu</component-class>
</component>
创建这个 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);
}
}
}
您可以像下面这样对监听器使用远程命令。
<p:remoteCommand name="test" id="test" actionListener"#{requestBean.listener}" update="@form" process="@form"></p:remoteCommand>
在 toggleselect 上调用以下代码方式
<p:ajax event="toggleSelect" oncomplete="test();" />
面临同样的问题,经过一段时间的搜索后我发现您可以将更新逻辑绑定到 onchange
属性。喜欢:
<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>
考虑:onchange="#{requestBean.listener()}"
你必须使用大括号调用监听器,否则你会得到一个异常。
不知道你用的是哪个版本的PF,不过我在this link中找到了解决办法。 (PrimeFaces 5.3)