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