In this tutorial we will show how to create a CDI search-powered Combo box which uses a JSF Converter to render the Objects in the Combo in text format and back into objects for processing.
Populating a combobox with a list of labels and values has been always a time consuming task for every developers. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem instances.
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}"> <f:selectItems value="#{page.names}"/> </h:selectOneMenu>
Then on the Managed Bean:
List<SelectItem> names = new ArrayList<SelectItem>(); //-- Populate list from database names.add(new SelectItem(valueObject,"label"));
This is fortunately not needed anymore since you can used custom ?Converters. ?Converters can be used for several purposes, the most obvious one is automatic conversion of a text format into a required one format, such as data time format
<h:inputText id="date" value="#{bean.date}" size="20" required="true" label="Enter Date" > <f:convertDateTime pattern="d-M-yyyy" /> </h:inputText>
Here’s an other example which can be used to force number conversion with a set of parameters:
<h:inputText id="salary" value="#{User.salary}"> <f:convertNumber maxFractionDigits="2" groupingUsed="true" currencySymbol="$" maxIntegerDigits="7" type="currency"/> </h:inputText>
On the other hand, a Custom data conversion is necessary if you need to convert field data into an application-specific value object. This can be the typical use case for a Combobox which needs to store data as Object (so that it can be further managed by the application), on the other hand it should be visibile in user-friendly text format.
Let’s see an example where a list of Customer Entities is added to a Combobox:
<h:panelGrid columns="3" columnClasses="titleCell"> <h:selectOneMenu value="#{requestController.customer}" converter="#{customerIdConverter}"> <f:selectItems value="#{requestController.customerList}" var="customer" itemValue="#{customer}" itemLabel="#{customer.name}" /> </h:selectOneMenu> </f:selectItems> </h:selectOneMenu> </h:panelGrid>
And here’s the relevant part from the RequestController Bean:
@Model public class RequestController { @Inject private ApplicationQuery query; @PostConstruct public void initNewRequest() { quantity = 0; customerList = query.findAllOrderedByName(); } private Customer customer; private List<Customer> customerList; public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } public List<Customer> getCustomerList() { return customerList; } public void setCustomerList(List<Customer> customerList) { this.customerList = customerList; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } // .. Other methods }
The Converter is a Simple class implementing the javax.faces.convert.Converter interfaces and annotated with the @javax.faces.convert.FacesConverter annotation:
package com.sample.converter; import javax.enterprise.inject.Model; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.convert.FacesConverter; import javax.inject.Inject; import com.packtpub.data.ApplicationQuery; import com.packtpub.model.Customer; @FacesConverter @Model public class CustomerIdConverter implements Converter { @Inject private ApplicationQuery query; @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { if (value == null || value.isEmpty()) { return null; } try { Object obj = query.findById(Long.valueOf(value)); return obj; } catch (Exception e) { e.printStackTrace(); throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Customer", value)), e); } } @Override public String getAsString(FacesContext context, UIComponent component, Object value) { if (!(value instanceof Customer)) { return null; } String s = String.valueOf(((Customer) value).getId()); return s; } }
Here, the method Object getAsObject(FacesContext context, UIComponent component, String value) converts the given string value into an object that is appropriate for storage in the given component.
On the other hand the method String getAsString(FacesContext context, UIComponent component, Object value) converts the given object, which is stored in the given component, into a string representation.
This is where the two methods are invoked into the JSF lifecycle:
The Entity Customer can be retrieved using an @ApplicationScoped CDI Bean that takes as parameter the id of the customer.
package com.sample;
import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import com.packtpub.model.Customer; import com.packtpub.model.Request; import java.util.List; @ApplicationScoped public class ApplicationQuery { @Inject private EntityManager em; public Customer findById(Long id) { return em.find(Customer.class, id); } public List<Customer> findAllOrderedByName() { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Customer> criteria = cb.createQuery(Customer.class); Root<Customer> member = criteria.from(Customer.class); criteria.select(member).orderBy(cb.asc(member.get("name"))); return em.createQuery(criteria).getResultList(); } }
Hope you enjoyed reading about JSF Converters. Would you like to go on reading about CDI ?