jsffaceletsselectonemenu

Retrieving selectOneMenu complex object as selected item


I'm beginning with JSF (Mojarra 2.2 and Glassfish 4) and currently practicing with a web application which job is to store Clients and their Orders in DB.

When creating a new Order, one feature is to allow choosing an existing client from a JSF <h:selectOneMenu>. An Order entity stores a Client entity among other attributes...

I've followed BalusC's great answer about prepopulating a <h:selectOneMenu> from a DB (here), and have successfully populated mine from data stored in an eager ApplicationScoped ManagedBean, but I can't manage to retrieve the selected item in the backing bean as complex object. It is always null.

This is driving me mad and your help will be truly appreciated! Here are the relevant code snippets:

@ManagedBean(eager = true)
@ApplicationScoped
public class Data implements Serializable {

    private static final long serialVersionUID = 1L;

    @EJB
    private ClientDao clientDao;

    private List<Client> clients;

    @PostConstruct
    private void init() {
        clients = clientDao.lister();
    }

    public List<Client> getClients() {
        return clients;
    }

}

Order creation bean (note: 'commande' means order ;)

@ManagedBean
@RequestScoped
public class CreerCommandeBean implements Serializable {
    private static final long serialVersionUID = 1L;
    private Commande commande;

    private String choixNouveauClient = "nouveauClient";

    @EJB
    private CommandeDao commandeDao;

    public CreerCommandeBean() {
        commande = new Commande();
    }

    public void inscrire() {

        System.out.println("client : " + commande.getClient()); // prints **NULL**
            // ... orderService to store in DB
    }
... getters and setters

Client converter:

@FacesConverter(value = "clientConverter", forClass = Client.class)
public class ClientConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null) {
            return null;
        }

        Data data = context.getApplication().evaluateExpressionGet(context, "#{data}", Data.class);
        for (Client c : data.getClients()) {
            if (c.getId().toString().equals(value)) {
                return c;
            }
        }
        throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Client", value)));
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return (value instanceof Client) ? String.valueOf(((Client) value).getId()) : null;
    }
}

Facelet excerpt:

<p:outputPanel id="gridContainerAncienClient">
            <p:selectOneMenu value="#{creerCommandeBean.commande.client}"
                rendered="#{creerCommandeBean.choixNouveauClient == 'ancienClient'}">
                <f:converter converterId="clientConverter" />
                <f:selectItems value="#{data.clients}" var="cli"
                    itemValue="#{cli}" itemLabel="#{cli.prenom} #{cli.nom}" />
            </p:selectOneMenu>
        </p:outputPanel>

Solution

  • CreerCommandeBean is @RequestScoped. That means it will live only for one request.
    When you select a client to be assigned to #{creerCommandeBean.commande.client} you do this by a request. #{creerCommandeBean.commande.client} is now the selected client. Then the request is over, the bean gets destroyed and your "changes" are lost.
    When you try to retrieve that data, you do that by a request again: A new instance of CreerCommandeBean is created and the constructor assigns the property commande with a new instance of Commande whose property client again is probably null.

    Solution:
    Use a broader scope. e.g. @ViewScoped which makes the bean "live" as long as you stay in the same view - no matter how many requests you make.

    Tip: Read BalusC's Post on Communication is JSF 2.0. Parts might be slightly different in JSF 2.2 but it's still a good and comprehensive introduction.