jsfjsf-2omnifacesselectmanycheckbox

h:selectManyCheckbox with omnifaces.SelectItemsConverter does not preselect the items


I'm using JSF 2.0, PrimeFaces and OmniFaces.

I have 2 dialogs with <h:selectManyCheckbox>. The first dialog creates a new Course:

enter image description here

The Disciplinas are presented as:

<h:selectManyCheckbox id="disciplinas" 
    value="#{cursoMBean.listaDisciplinasDoCurso}"
    converter="omnifaces.SelectItemsConverter">
    <f:selectItems value="#{cursoMBean.listaTodasDisciplinas}"
        var="disciplina" itemValue="#{disciplina}"
        itemLabel="#{disciplina.nome}" />
</h:selectManyCheckbox>

This works fine. When I select some disciplines and submit the form, then the new Course with the selected Disciplines is properly inserted in the DB.

However, when I try to retrieve an existing Course from the DB, the saved Disciplines are not preselected.

enter image description here

The code is the same:

<h:selectManyCheckbox id="disciplinas" 
    value="#{cursoMBean.listaDisciplinasDoCurso}"
    converter="omnifaces.SelectItemsConverter">
    <f:selectItems value="#{cursoMBean.listaTodasDisciplinas}"
        var="disciplina" itemValue="#{disciplina}"
        itemLabel="#{disciplina.nome}" />
</h:selectManyCheckbox>

Here's the backing bean:

private ArrayList<Disciplina> listaTodasDisciplinas;
private ArrayList<Disciplina> listaDisciplinasDoCurso;

public CursoMBean() {
    if (listaTodasDisciplinas == null) {
        listaTodasDisciplinas = controleDisciplina.consulta();
    }

    if (listaDisciplinasDoCurso == null) {
        listaDisciplinasDoCurso = new ArrayList<Disciplina>();
    }
}

// When user selects one Course to edit, this method is called:
public void setSelecionado(Curso selecionado) {
    this.selecionado = selecionado;

    if (selecionado != null) {
        listaTodasDisciplinas = controleDisciplina.consulta();
        listaDisciplinasDoCurso = controleCurso.listaDisciplinasAssociadas(selecionado);
    }
}

Here's the Disciplina entity:

public class Disciplina {

    private int id;
    private String nome;

    public Disciplina() {
    }

    public Disciplina(int id, String nome) {
        this.id = id;
        this.nome = nome;
    }

    public int getId() {
        return id;
    }

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

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        if (!(nome.isEmpty() || nome == " " || nome == "  ")){
            this.nome = nome;
        }
    }

}

How is this caused and how can I solve it?


Solution

  • By default, the SelectItemsConverter relies on toString() of the entity to match the selected items. Your entity however doesn't have a toString() implemented and is thus relying on default fqn@hashcode result which is not the same when two physically different Disciplina instances are created even though they have the same value.

    You've basically 2 options, also hinted in SelectItemsConverter showcase and javadoc:

    1. Implement a toString method that uniquely identifies the entity and which makes sense as an identifier. For example,

      @Override
      public String toString() {
          return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
      }
      

      (note that this toString() is designed such that you can easily keep it in an abstract base class of all your entities, so that you don't need to copypaste the same over all your entities)

    2. Or, if implementing such a toString() is not an option for some reason (e.g. relying on generated classes which can't be modified afterwards (neither the generator template)), then extend the converter as follows:

      @FacesConverter("disciplinaSelectItemsConverter")
      public class DisciplinaSelectItemsConverter extends SelectItemsConverter {
      
          @Override
          public String getAsString(FacesContext context, UIComponent component, Object value) {
              Integer id = (value instanceof Disciplina) ? ((Disciplina) value).getId() : null;
              return (id != null) ? String.valueOf(id) : null;
          }
      
      }
      

      (note: you should really be using Integer instead of int as ID, the int cannot be null which is the correct way to represent a brand new and unpersisted entity)

      And use it as follows

      <h:selectManyCheckbox ... converter="disciplinaSelectItemsConverter">