Primefaces selectBooleanCheckbox值在ajax动作上丢失

时间:2017-05-23 13:41:13

标签: ajax primefaces jsf-2

我在这里发布MCVE,因为我对JSF行为感到困惑。 我有index.xhtml:

<h:form id="entityForm" enctype="multipart/form-data" styleClass="entityForm">
<p:dataTable id="dataTableId1" value="#{testBean.testEntityList}" var="tent" rowKey="#{tent.id}">
    <p:column style="width:16px">
        <p:rowToggler/>
    </p:column>
    <p:column>
        <h:outputText value="#{tent.id}"/>
    </p:column>
    <p:column>
        <h:outputText value="#{tent.name}"/>
    </p:column>
    <p:rowExpansion>
        <p:selectBooleanCheckbox value="#{tent.something}"/>
        <p:inputText value="#{tent.strNextToSomethingCheckBox}" />
    </p:rowExpansion>
</p:dataTable>
<p:commandButton id="button1" ajax="true" value="Do something" update="entityForm"/>
</h:form>

bean代码:

@ManagedBean(name = "testBean")
@ViewScoped
public class TestBean implements Serializable {

private static final Logger LOG = Logger.getLogger(TestBean.class);

private List<TestEntity> testEntityList;

@PostConstruct
public void init() {
    LOG.debug("init call");
    testEntityList = new ArrayList<>();

    testEntityList.add(new TestEntity(1L, "Entry 1", true, "Value 1 str"));
    testEntityList.add(new TestEntity(2L, "Entry 2", false, "Value 2 str"));
    testEntityList.add(new TestEntity(3L, "Entry 3", false, "Value 3 str"));

}

public class TestEntity {

    Long id;

    String name;

    Boolean something;

    String strNextToSomethingCheckBox;


    public TestEntity(Long id, String name, Boolean something, String strNextToSomethingCheckBox) {
        this.id = id;
        this.name = name;
        this.something = something;
        this.strNextToSomethingCheckBox = strNextToSomethingCheckBox;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Boolean getSomething() {
        return something;
    }

    public void setSomething(Boolean something) {
        LOG.debug("setSomething call");
        this.something = something;
    }

    public String getStrNextToSomethingCheckBox() {
        return strNextToSomethingCheckBox;
    }

    public void setStrNextToSomethingCheckBox(String strNextToSomethingCheckBox) {
        LOG.debug("setStrNextToSomethingCheckBox call");
        this.strNextToSomethingCheckBox = strNextToSomethingCheckBox;
    }
}


public List<TestEntity> getTestEntityList() {
    return testEntityList;
}

public void setTestEntityList(List<TestEntity> testEntityList) {
    this.testEntityList = testEntityList;
}

}

场景:当您打开页面时,展开第一行,您将看到复选框状态为已选中,当您按下按钮,表单已更新且复选框仍处于选中状态时(您需要再次展开行以查看该行)

有问题的场景:打开页面,不展开任何行,只需单击按钮,在表单更新后,展开第一行,您将看到该值从true设置为false

问题:为什么值设置为true为false?这个错误或预期的行为?我怎么能避免这个? 感谢。

1 个答案:

答案 0 :(得分:0)

您可以参考下面的主要SelectBooleanCheckboxRenderer#decode代码:

@Override
public void decode(FacesContext context, UIComponent component) {
    SelectBooleanCheckbox checkbox = (SelectBooleanCheckbox) component;

    if(checkbox.isDisabled()) {
        return;
    }

    decodeBehaviors(context, checkbox);

    String clientId = checkbox.getClientId(context);
    String submittedValue = (String) context.getExternalContext().getRequestParameterMap().get(clientId + "_input");

    if(submittedValue != null && isChecked(submittedValue)) {
        checkbox.setSubmittedValue(true);
    }
    else {
        checkbox.setSubmittedValue(false);
    }
}

将值从true设置为false的原因是,当p:rowExpansion关闭时,p:selectBooleanCheckbox中未设置RequestParameterMap。因此,当p:selectBooleanCheckbox被解码时,submittedValue变为空。因此,执行checkbox.setSubmittedValue(false)

展开p:rowExpansion后,您可以看到p:selectBooleanCheckbox的值在RequestParameterMap中。 您可以使用bean中的follow方法将action="#{testBean.doSomething}"添加到p:commandButton来确认这一点:

public void doSomething() {
    FacesContext context = FacesContext.getCurrentInstance();
    Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
    Set<String> keySet = requestParameterMap.keySet();

    for (String key : keySet) {
        LOG.debug("key>>" + key);
        LOG.debug("value>>" + requestParameterMap.get(key));
    }
}

请注意,p:selectBooleanCheckbox在取消选中RequestParameterMap时也会设置为

你可以通过覆盖SelectBooleanCheckboxRenderer#decode来解决这个问题并将其解决这个问题:

if(submittedValue != null && isChecked(submittedValue)) {
    checkbox.setSubmittedValue(true);
}
else {
    checkbox.setSubmittedValue(false);
}

到此:

if (submittedValue != null && isChecked(submittedValue)) {
    checkbox.setSubmittedValue(true);
} else {
    if (checkbox.isRendered()) {
        checkbox.setSubmittedValue(false);
    }
}

这样,只有在取消选中p:selectBooleanCheckbox并进行渲染时,该值才为false。

修改

对不起。我的覆盖解决方案不正确但想法是你需要覆盖SelectBooleanCheckboxRenderer#decode

我会在可用时发布正确的代码。

编辑2:

似乎无法覆盖SelectBooleanCheckboxRenderer#decode,所以我尝试了不同的方法。

我在TestEntity中添加了一个标志(切换),初始值为false。 这将用作在p:selectBooleanCheckbox内呈现p:rowExpansion的标记 当使用bean中的p:ajax event="rowToggle"onRowToggle方法切换(VISIBLE)行时,此标志将设置为true。

我们的想法是,SelectBooleanCheckboxRenderer#decode在未呈现p:selectBooleanCheckbox时不会被执行。 这将导致保留该值。这种方法 NOT 需要覆盖SelectBooleanCheckboxRenderer#decode

以下是代码:

<强> XHTML:

<h:form id="entityForm" enctype="multipart/form-data" styleClass="entityForm">
<p:dataTable id="dataTableId1" value="#{testBean.testEntityList}" var="tent" rowKey="#{tent.id}">

    <p:ajax event="rowToggle" listener="#{testBean.onRowToggle}"/>

    <p:column style="width:16px">
        <p:rowToggler/>
    </p:column>
    <p:column>
        <h:outputText value="#{tent.id}"/>
    </p:column>
    <p:column>
        <h:outputText value="#{tent.name}"/>
    </p:column>
    <p:rowExpansion>
        <p:selectBooleanCheckbox value="#{tent.something}" rendered="#{tent.toggled}"/>
        <p:inputText value="#{tent.strNextToSomethingCheckBox}" />
    </p:rowExpansion>
</p:dataTable>
<p:commandButton id="button1" ajax="true" value="Do something" update="entityForm"/>
</h:form>

<强>豆:

@ManagedBean(name = "testBean")
@ViewScoped
public class TestBean implements Serializable {

    private static final Logger LOG = Logger.getLogger(TestBean.class);

    private List<TestEntity> testEntityList;

    @PostConstruct
    public void init() {
        LOG.debug("init call");
        testEntityList = new ArrayList<>();

        testEntityList.add(new TestEntity(1L, "Entry 1", true, "Value 1 str", false));
        testEntityList.add(new TestEntity(2L, "Entry 2", false, "Value 2 str", false));
        testEntityList.add(new TestEntity(3L, "Entry 3", false, "Value 3 str", false));
    }

    public class TestEntity {

        Long id;

        String name;

        Boolean something;

        String strNextToSomethingCheckBox;

        boolean toggled;

        public TestEntity(Long id, String name, Boolean something, String strNextToSomethingCheckBox, boolean toggled) {
            this.id = id;
            this.name = name;
            this.something = something;
            this.strNextToSomethingCheckBox = strNextToSomethingCheckBox;
            this.toggled = toggled;
        }

        // getters and setters
    }

    public List<TestEntity> getTestEntityList() {
        return testEntityList;
    }

    public void setTestEntityList(List<TestEntity> testEntityList) {
        this.testEntityList = testEntityList;
    }

    public void onRowToggle(ToggleEvent event) {
        TestEntity entity = (TestEntity) event.getData();

        if (event.getVisibility() == Visibility.VISIBLE) {
            entity.setToggled(true);
        } else {
            entity.setToggled(false);
        }
    }
}

希望这有帮助。