【问题标题】:PrimeFaces selectCheckboxMenu: Old values in toggleSelect actionListenerPrimeFaces selectCheckboxMenu:toggleSelect actionListener 中的旧值
【发布时间】:2015-06-27 02:29:20
【问题描述】:

在我的 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>

现在让我们考虑以下用例:您正在进入视图,选择了“一”和“二”。如果我点击“全选”复选框,结果是:

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

但预期的结果应该是这样的:

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

对于常规更改事件,它按预期工作。在这里,我在侦听器中获得了新的选择。我该如何解决?或者我做错了什么?


GlassFish 4.1,在 Java 1.8.0_45 上运行

JSF 2.2.10 (Mojarra)

PrimeFaces 5.1

OmniFaces 1.8.1

【问题讨论】:

    标签: jsf-2 primefaces


    【解决方案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]
    

    最后一个是工具选择,你可以看到模型已经正确更新,但是之前调用了监听器。

    让我们多玩一点自定义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 中通知。

    另请参阅:

    【讨论】:

    • 谢谢,我现在看到并理解这种行为。但是,如果能够在 bean 中提供完整的选择选项列表,则可以使用 ToggleSelectEvent 来解决问题:public void toggle(ToggleSelectEvent te) { if(te.isSelected()) { // do sth with this.items }} ...也许这对 s.o. 也有帮助。否则:)
    • 使用没有问题,只要在最后执行一个完整的表单提交,导致模型正常刷新。例如,如果您想同时显示带有所选项目的消息,则问题可能是这样。 PF 没有在他们的展示中显示这种行为,而他们这样做了in other components like pickList
    • 嗯?当然这是一个问题......如前所述,我想执行一些取决于所选值的业务逻辑。如果动作侦听器被调用之前 ro apply_request_values 和 process_validations,那么我正在使用错误的数据执行此逻辑。但是这里提供的解决方法对我来说很好......但是,我同意这是 PF 中的一个错误,我将报告它。让我们看看 PF 的人在想什么:)
    • 那一定是个bug。我已经在 v5.2 中并且它一直在发生,所以值得为它打开一个问题。对于您的解决方法,如果您可以制作一个示例并将其作为答案发布,那将是很好的,而 PF 家伙会修复它;-)
    • 我检查了他们的变更日志,似乎错误 (github.com/primefaces/primefaces/issues/318) 已在 5.2.4 中修复。不幸的是,我仍然坚持使用 PF5.2
    【解决方案2】:

    侦听器在 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();
            }
        }
    }
    

    【讨论】:

    • 有点晚了,但仍然发布了提到 BalusC 的解决方法,它不适用于 PF 的组件。
    • 为我工作..谢谢
    【解决方案3】:

    我偶然发现了另一种解决方法:基本上,您需要从值的设置器中填充标签文本。例如:

    查看

    <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...
    }
    

    【讨论】:

      【解决方案4】:

      我也遇到过这个问题。它在 PrimeFaces 5.2 中仍未修复。我的解决方案如何解决它:

      添加到 faces-config.xml

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

      创建这个类:

      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);
              }
          }
      
      }
      

      【讨论】:

        【解决方案5】:

        您可以使用远程命令作为监听器,如下所示。

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

        在toggleselect上调用以下代码

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

        【讨论】:

          【解决方案6】:

          遇到同样的问题,经过一段时间的搜索,我发现您可以将更新逻辑绑定到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)

          【讨论】:

          • 这是错误的,onchange是客户端js回调。
          猜你喜欢
          • 1970-01-01
          • 2014-06-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-12
          相关资源
          最近更新 更多