A common issue regarding JSF converters and the h:selectOneMenu
component can be recreated in a simple scenario. Let's suppose that we are in the following situation: we have a database table that contains a number of rows that define cars. Each row has an Integer
value representing the car number and a string
value representing the car name. Obviously this table is wrapped into a managed bean, as shown next:
package cars; import javax.faces.bean.ManagedBean; @ManagedBean public class CarBean { private Integer carNumber; private String carName; public CarBean() {} public CarBean(Integer carNumber, String carName){ this.carNumber=carNumber; this.carName=carName; } public Integer getCarNumber(){ return this.carNumber; } public void setCarNumber(Integer carNumber){ this.carNumber=carNumber; } public String getCarName(){ return this.carName; } public void setCarName(String carName){ this.carName=carName; } }
Going further, let's have another managed bean that contains a collection of cars (we simulate the table database with a few manual instances), as shown next:
package cars; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.model.SelectItem; @ManagedBean(name = "carsBean") @SessionScoped public class CarsBean { private HashMap<Integer, CarBean> myCars = new HashMap<Integer, CarBean>(); private List<SelectItem> carItems = new LinkedList<SelectItem>(); private CarBean selectedCar; public CarsBean() { CarBean car_1 = new CarBean(1, "Ferrari"); CarBean car_2 = new CarBean(2, "Logan"); CarBean car_3 = new CarBean(3, "Fiat"); CarBean car_4 = new CarBean(4, "Kia"); CarBean car_5 = new CarBean(5, "Skoda"); carItems.add(new SelectItem(car_1, car_1.getCarName())); myCars.put(car_1.getCarNumber(), car_1); carItems.add(new SelectItem(car_2, car_2.getCarName())); myCars.put(car_2.getCarNumber(), car_2); carItems.add(new SelectItem(car_3, car_3.getCarName())); myCars.put(car_3.getCarNumber(), car_3); carItems.add(new SelectItem(car_4, car_4.getCarName())); myCars.put(car_4.getCarNumber(), car_4); carItems.add(new SelectItem(car_5, car_5.getCarName())); myCars.put(car_5.getCarNumber(), car_5); } public CarBean getCar(Integer number) { return (CarBean) myCars.get(number); } public List<SelectItem> getCarItems() { return carItems; } public void setCarItems(List<SelectItem> carItems) { this.carItems = carItems; } public CarBean getSelectedCar() { return this.selectedCar; } public void setSelectedCar(CarBean selectedCar) { this.selectedCar = selectedCar; } }
Now, we can render our car collection using an h:selectOneMenu
component, as shown next:
<h:form id="selectCarFormID"> <h:selectOneMenu id="carsID" value="#{carsBean.selectedCar}"> <f:selectItems value="#{carsBean.carItems}"/> </h:selectOneMenu> <h:commandButton value="Submit" action="selected?faces- redirect=true"/> </h:form>
Well, the car list is rendered ok, as you can see the list and make a selection. However, the problem occurs when we choose a car and we try to populate the selectedCar
property with it. As you see, the selectedCar
is a CarBean
instance, while the submitted information represents an integer (the car number). Therefore, we need to convert this integer to a CarBean
, before it gets rendered, as shown next:
<h:outputText value="Selected car number:"/> <h:outputText value="#{carsBean.selectedCar.carNumber}"/> <br /> <h:outputText value="Selected car name:"/> <h:outputText value="#{carsBean.selectedCar.carName}"/>
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
The solution came from a custom converter. In the getAsString
object, we extract and return the car number, and in the getAsObject
method, the submitted car number is converted into a CarBean
instance, as shown in the following code:
package cars; import javax.el.ValueExpression; 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; @FacesConverter(value = "carConverter") public class CarConverter implements Converter { public String getAsString( FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null){throw new NullPointerException("context");} if (arg1 == null){throw new NullPointerException("component");} return ((CarBean)arg2).getCarNumber().toString(); } public Object getAsObject( FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null){throw new NullPointerException("context");} if (arg1 == null){throw new NullPointerException("component");} FacesContext ctx = FacesContext.getCurrentInstance(); ValueExpression vex = ctx.getApplication().getExpressionFactory() .createValueExpression(ctx.getELContext(), "#{carsBean}",CarsBean.class); CarsBean cars = (CarsBean)vex.getValue(ctx.getELContext()); CarBean car; try { car = cars.getCar(new Integer (arg2)); } catch( NumberFormatException e ) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unknown value", "This is not a car number!" ); throw new ConverterException( message ); } if( car == null ) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "Unknown value", "The car is unknown!" ); throw new ConverterException( message ); } return car; } }
The mechanism is pretty simple! First, the collection of cars is rendered using a SelectItem
object. Every single car will pass through the converter's getAsString
method and is added to the list. Notice that the getAsString
method extracts and returns the car number for each car.
Second, when a car is selected and submitted, the selected car number arrives into the getAsObject
method. There we search for the corresponding car into our myCars
map. Once the car is found it is returned into the setSelectedCar
method.
You can use the same technique for h:selectManyCheckbox
or h:selectManyListbox
. For example, in the case of h:selectManyCheckbox
, you will render the list in the following way:
<h:form id="selectCarFormID"> <h:selectManyCheckbox id="carsID" value="#{carsBean.selectedCar}" converter="carConverter"> <f:selectItems value="#{carsBean.carItems}"/> </h:selectManyCheckbox> <h:commandButton value="Submit" action="selected?faces-redirect=true"/> </h:form>
And the selections can be rendered, as shown next:
<h:dataTable value="#{carsBean.selectedCar}" var="item"> <h:column> <f:facet name="header"> <h:outputText value="Car Name:"/> </f:facet> <h:outputText value="#{item.carName}"/> </h:column> </h:dataTable>