13 April 2010

Using Converter with SelectOneMenu in JSF

Using Converter with SelectOneMenu in JSF

I were working in some use case that required me to use JSF Converters with SelectOneMenu component, I've searched for the solution, but really I ended messy without any helpful and "clean" result.

So, I decided to write this topic to be a clean guide on how to use Converters with Lists in general and SelectOneMenu in particular.

As you know, all input controls in html form accepts only character Strings, you cannot enter an Object in some textbox for example.
So, Converts in JSF enables you to convert from String to some Object and back from some Object to String.

Suppose you have a List of Object Faculty and you need to represent it in a SelectOneMenu control (dropdown list), one way is to create this SelectOneMenu and backed it to an integer value, and I prefer that way if your use case can goes with it.

Example,

here's the JSF snipet:

<h:selectOneMenu value="#{registerBean.faculty}">
<f:selectItems value="#{registerBean.facultyItems}" />
</h:selectOneMenu>



And here's the backing bean:

public class RegisterBean{
private String faculty;
private List<SelectItem> facultyItems;

// getter & setter for faculty
// ....

//setter for facultyItems;
...

//getter for facultyItems
public List<SelectItem> getFacultyItems(){
if (facultyItems == null){
facultyItems = new ArrayList<SelectItem>();
FacultyService fs = ServiceFactory.getFacultyService();
List<Faculty> facList = fs.getAllFaculties();
for (Faculty fac : facList){
facultyItems.add(new SelectItem(
fac.getId()
, fac.getName()); // value, label, the value to choose and label to appear fo the user
}
}
return facultyItems;
}
}


And then, when the use select the faculty, the id corrosponding to it is being assigned to the varaible faculty.
This is the best way I suggest if your use case that simple, Don't try to drill in the issue of Converting from Objects to String and vice versa if you can.
Note, here you mapped the integer Id to the String that contains the value of the html SELECT tag:

<select >
<option value="1">التجاره</option>
<option value="2">الأداب</option>
<option value="3">السياحه و الفنادق</option>
<option value="4">الحقوق</option>
<option value="5">العلوم</option>
<option value="6">الهندسه</option>
<option value="7">التربيه</option>
</select>


So, the getFaculty() property on the bean returns one value from 1 to 7, this that we assigned in the 7th line in the method getFacultyItems()


But if you have to do so, Here's the way.
Let's try to rewrite the above example by using Converters,
Suppose you want to back the SelectOneMenu directly to Faculty Object, the JSF Page will still as is -for now, but we need to modify the backing bean first:

public class RegisterBean{
private Faculty faculty;
private List<SelectItem> facultyItems;

// getter & setter for faculty
// ....

//setter for facultyItems;
...

//getter for facultyItems
public List<SelectItem> getFacultyItems(){
if (facultyItems == null){
facultyItems = new ArrayList<SelectItem>();
FacultyService fs = ServiceFactory.getFacultyService();
List<Faculty> facList = fs.getAllFaculties();
for (Faculty fac : facList){
facultyItems.add(new SelectItem(
fac // NOTE THIS CHANGE, we used Faculty object instead of its id property
, fac.getName()); // value, label, the value to choose and label to appear fo the user
}
}
return facultyItems;
}
}


So, we can get the Faculty object directly instead of getting its Id and then looking up it in the database.
That is not all the story, it is the start, just continue reading...

In the first example, we told the JSF to convert from/to int (the type of Id) to String (the value appears on the form), and JSF can handle this very well, but in the second example, we need to tell JSF to convert from/to Faculty to String.

To do so, we need to implement javax.faces.convert.Converter interface, here's the code:

package com.forat.web.converters;

import java.util.ResourceBundle;

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 com.forat.model.Faculty;
import com.forat.web.util.StaticInitializer;

public class FacultyConverter implements Converter {

@Override
public Object getAsObject(FacesContext context, UIComponent component, String newValue) {
Faculty faculty = null;
try {
faculty = StaticInitializer.getPreLoadedFaculty(Integer.parseInt(newValue));
}catch(Throwable ex) {
ResourceBundle bundle = ResourceBundle.getBundle("messages");
FacesMessage msg = new FacesMessage(bundle.getString("faculty_convertion_message"));
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(msg);
}
return faculty;
}

@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
String val = null;
try {
Faculty fac = (Faculty) value;
val = Integer.toString(fac.getId());
}catch(Throwable ex) {
ResourceBundle bundle = ResourceBundle.getBundle("messages");
FacesMessage msg = new FacesMessage(bundle.getString("faculty_convertion_message"));
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(msg);
}
return val;
}

}


The "getAsObject()" method is responsble for the conversion between the page and the backing bean (when the request submitted and to be precessed on the server.
The method "getAsString()" is responsble for the conversion between the Server and the html page (when the rendering process occure to display the value of the object)


When the request is recived by the Server (JSF ) we tell JSF in "getAsObject()" to:
faculty = StaticInitializer.getPreLoadedFaculty(Integer.parseInt(newValue));

take the value sent and get the corrosponding object from somewhere (the database for example).
notice the try/catch block, we have to throw ConverterException in case of conversion errors.

When the response to be rendered, in "getAsString()", we need to tell JSF to instead of display the entire object, just to display the unique identier of it (the id property)

Now we need to register this Converter, we can do this in many ways, for example by registering it in faces-conf.xml:

<converter>
<converter-id>com.forat.web.converters.Faculty</converter-id>
<converter-class>com.forat.web.converters.FacultyConverter</converter-class>
</converter>



And in the JSF page, we need to tell in the SelectOneMenu tag to use this conveter:
<h:selectOneMenu value="#{employee.faculty}">
<f:converter converterId="com.forat.web.converters.Faculty" />
<f:selectItems value="#{registerBean.facultyItems}" />
</h:selectOneMenu>


That's all, hope this post to be usful for you.

15 comments:

mhewedy said...

@My Computer:
Great site, I hope I can do it one day :)

Nathan said...

Where is the code for StaticInitializer.getPreLoadedFaculty?

mhewedy said...

@Nathan:
`take the value sent and get the corresponding object from somewhere (the database for example).`

Actually, I didn't put it, but as I said, consider it as some method that look up the database by the id and return the corresponding Faculty object.

Anonymous said...

Thank you very much. Works like charm.

mhewedy said...

@Anonymous:
Welcome ...

Unknown said...

Thank you very much...

Anonymous said...

Hi. Great !!
but ... when I select an element from the SelectOneMenu it send an error message "Validation Error: value is not valid"
i just add a primefaces error message to capture and display










it breaks the execution. Why could this be happening ?

Deniz said...

Hi,
first thanks for the great explanation, I was looking to web for an explanation of this topic but couldn't find a good one, now I have one!
I have a question: when using getAsString method, isn't this code:
val = Integer.toString(fac.getId());
should be
val = fac.getName()
? Because when we are rendering the selectOneMenu we are using fac.getName..

Anonymous said...

Congrats !!!
Nice one...

Rodrigo Osorio López said...

Tks for the help!!! it works fine

Anonymous said...

is it possible to do this, when bean is in request scope or has to be in session scope?

Unknown said...

Sir;

Gud day! Can you help me with my problem on selectOneMenu. Your help is greatly appreciated.

here is the link:
http://stackoverflow.com/questions/12329623/selectonemenu-java-lang-nullpointerexception-when-adding-record-to-the-databas

Tnx again.

rogie

Unknown said...

Sir;

Thank you very much for such a wonderful post on selectOneMenu.

Greatly appreciated.

More power.

rogie

Benjamin said...

I have similar problem, and the list isn't populated with the selected object by default when page is loaded. I'm using primefaces 3.4

Benjamin said...

See solutions here http://balusc.blogspot.com/2007/09/objects-in-hselectonemenu.html?m=1

Need to add equals(), hashCode () on the select objects.