1

I have two dropdowns: One is implemented using a selectOneMenu component and the other is implemented with selectCheckboxMenu.

When selecting an option in the selectOneMenu, the option values of the selectCheckboxMenu are updated depending of what option was selected in the selectOneMenu.

The following scenario happens:

  1. I select an option of the selectOneMenu, the selectCheckboxMenu gets populated
  2. I select/check some options of the selectCheckboxMenu
  3. I select another option of the selectOneMenu, the selectCheckboxMenu gets populated with other values
  4. I select some of the new values
  5. I select the option in step 1 of the selectOneMenu
  6. The values I selected in step 2 are no longer selected

I believe this is because the list value bound to the selectCheckboxMenu is getting reset by the setter method.

What I would like is for the state of the selectCheckboxMenu to be globally saved. What would be the best strategy for doing this?

EDIT:

Here's the relevant code that duplicates the previous behavior:

Bean:

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

@ManagedBean
@ViewScoped
public class BackingBean {

    private List<Parent> parents = new ArrayList<>();
    private List<Child> children = new ArrayList<>();
    private List<Child> selectedChildren = new ArrayList<>();
    private Parent selectedParent = new Parent();

    @Inject
    FamilyService service;

    @PostConstruct
    public void init(){
        parents = service.findParents();
    }


    public void parentChanged(){
        children = new ArrayList<>();
        if(getSelectedParent() == null){
            return;
        }

        children = service.findChildrenByParent(getSelectedParent());
    }

    public void selectedSonChanged(){
        System.out.println(selectedChildren.size());
    }

    public List<Parent> getParents() {
        return parents;
    }

    public void setParents(List<Parent> parents) {
        this.parents = parents;
    }

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

    public List<Child> getSelectedChildren() {
        return selectedChildren;
    }

    public void setSelectedChildren(List<Child> selectedChildren) {
        this.selectedChildren = selectedChildren;
    }

    public Parent getSelectedParent() {
        return selectedParent;
    }

    public void setSelectedParent(Parent selectedParent) {
        this.selectedParent = selectedParent;
    }
}

XHTML:

<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form id="selectOne">
    <p:messages autoUpdate="true" />

        <h:outputLabel for="parents" value="Parents:" />
        <p:selectOneMenu converter="#{parentConverter}" id="parents" value="#{backingBean.selectedParent}">
            <p:ajax update="children" listener="#{backingBean.parentChanged}" />
            <f:selectItem noSelectionOption="true" value="#{null}" itemLabel="None" />
            <f:selectItems value="#{backingBean.parents}" var="p" itemLabel="#{p.name}" itemValue="#{p}" />
        </p:selectOneMenu>


        <h:outputLabel for="children" value="Children:" />

        <p:selectCheckboxMenu converter="#{childConverter}"  id="children"
                              value="#{backingBean.selectedChildren}"
                              label="Children" filter="true" filterMatchMode="startsWith">
            <p:ajax update="display" listener="#{backingBean.selectedSonChanged}" />
            <f:selectItems value="#{backingBean.children}" var="c" itemLabel="#{c.name}" itemValue="#{c}" />
        </p:selectCheckboxMenu>


        <p:outputPanel id="display">
            <p:dataList value="#{backingBean.selectedChildren}" var="sn" emptyMessage="No children selected">
                #{sn.name}
            </p:dataList>
        </p:outputPanel>

        <p:commandButton value="Submit" update="display, selectOne" />

</h:form>

</h:body>
</html>

The complete project is here: https://github.com/cenobyte321/jsfsamples/tree/master/selectcheckboxmenu

You can find a video of the interaction mentioned previously here: https://github.com/cenobyte321/jsfsamples/blob/master/selectcheckboxmenu/example1.mp4?raw=true

As you can see when choosing another "Parent" and then selecting their children the previous children list gets substituted. How can I properly modify this behavior so the previously selected elements get persisted and shown as checked in the selectCheckboxMenu?

EDIT 2:

One solution I found was introducing another variable which will hold the global values and will be modified in the listener method "selectedSonChanged". Here's the relevant code:

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@ManagedBean
@ViewScoped
public class BackingBean {

    private List<Parent> parents = new ArrayList<>();
    private List<Child> children = new ArrayList<>();
    private List<Child> selectedChildren = new ArrayList<>();

    private List<Child> globalSelectedChildren = new ArrayList<>();
    private Parent selectedParent = new Parent();

    @Inject
    FamilyService service;

    @PostConstruct
    public void init(){
        parents = service.findParents();
    }


    public void parentChanged(){
        children = new ArrayList<>();
        if(getSelectedParent() == null){
            return;
        }

        children = service.findChildrenByParent(getSelectedParent());
        selectedChildren = globalSelectedChildren.stream().filter(c -> c.getParent().equals(selectedParent)).collect(Collectors.toList());
        System.out.println(selectedChildren.size());
    }

    public void selectedSonChanged(){
        System.out.println(selectedChildren.size());
        globalSelectedChildren = globalSelectedChildren.stream().filter(c -> !c.getParent().equals(selectedParent)).collect(Collectors.toList());
        globalSelectedChildren.addAll(selectedChildren);
    }

    public List<Parent> getParents() {
        return parents;
    }

    public void setParents(List<Parent> parents) {
        this.parents = parents;
    }

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

    public List<Child> getSelectedChildren() {
        return selectedChildren;
    }

    public void setSelectedChildren(List<Child> selectedChildren) {
        this.selectedChildren = selectedChildren;
    }

    public Parent getSelectedParent() {
        return selectedParent;
    }

    public void setSelectedParent(Parent selectedParent) {
        this.selectedParent = selectedParent;
    }

    public List<Child> getGlobalSelectedChildren() {
        return globalSelectedChildren;
    }

    public void setGlobalSelectedChildren(List<Child> globalSelectedChildren) {
        this.globalSelectedChildren = globalSelectedChildren;
    }
}

XHTML:

<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form id="selectOne">
    <p:messages autoUpdate="true" />

        <h:outputLabel for="parents" value="Parents:" />
        <p:selectOneMenu converter="#{parentConverter}" id="parents" value="#{backingBean.selectedParent}">
            <p:ajax update="children" listener="#{backingBean.parentChanged}" />
            <f:selectItem noSelectionOption="true" value="#{null}" itemLabel="None" />
            <f:selectItems value="#{backingBean.parents}" var="p" itemLabel="#{p.name}" itemValue="#{p}" />
        </p:selectOneMenu>


        <h:outputLabel for="children" value="Children:" />

        <p:selectCheckboxMenu converter="#{childConverter}"  id="children"
                              value="#{backingBean.selectedChildren}"
                              label="Children" filter="true" filterMatchMode="startsWith">
            <p:ajax update="display" listener="#{backingBean.selectedSonChanged}" />
            <f:selectItems value="#{backingBean.children}" var="c" itemLabel="#{c.name}" itemValue="#{c}" />
        </p:selectCheckboxMenu>


        <p:outputPanel id="display">
            <p:dataList value="#{backingBean.globalSelectedChildren}" var="sn" emptyMessage="No children selected">
                #{sn.name}
            </p:dataList>
        </p:outputPanel>

        <p:commandButton value="Submit" update="display, selectOne" />

</h:form>

</h:body>
</html>

Here's the branch with this solution: https://github.com/cenobyte321/jsfsamples/tree/solution-1/selectcheckboxmenu

Cenobyte321
  • 469
  • 1
  • 8
  • 26

0 Answers0