Using custom converters for h:selectOneMenu

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}"/>

Getting ready

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.

How to do it...

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;
}
}

How it works...

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.

There's more...

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>

See also

The code bundled with this book contains a complete example of this recipe. The project can be opened with NetBeans 6.8 and is named: Using_custom_converters_for_selectOneMenu_1 and Using_custom_converters_for_selectOneMenu_2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset