问题
I want to get selected object from <h:selectOneMenu>
, but the problem is I couldn't find any generic converter for all type of entities.
My first question is, is there a generic converter for all type of entities? I don't want to write another converter again for each other entity. My second question is, is there a way to get selected object without any converter? I don't want to call the DB again and again.
I have a Car
entity with id
and name
properties.
回答1:
My first question is, is there a generic converter for all type of entities?
This does indeed not exist in standard JSF. The JSF utility library OmniFaces has such a converter in its assortiment, the omnifaces.SelectItemsConverter
. All you need to do is to declare it as converter of an UISelectOne
or UISelectMany
component as follows:
<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">
See also the SelectItemsConverter showcase. This converter relies on the toString()
of the object item. There's also another one, the omnifaces.SelectItemsIndexConverter
, which relies instead on the index of the object item in the options list, see also the SelectItemsIndexConverter showcase.
There are currently no other JSF component/utility libraries offering the same.
Second question is, is there a way to get selected object without any converter?
No. Just use the OmniFaces one so that you don't need to create a custom converter which hits the DB. Or if you want to go overboard, create a custom renderer for <h:selectOneMenu>
which renders the item index as option value and is able to set it as model value, but that's a lot of more work than a simple converter and you'd still need to do some additional work in order to get the desired object from the list based on the index — which just doesn't make any sense.
See also:
- How to populate options of h:selectOneMenu from database?
- Conversion Error setting value for 'null Converter'
- Implement converters for entities with Java Generics
回答2:
It seems like there should be a generic converter so that you can easily select the object from the drop down list without having to write a converter for every object type and without having to call the database (as most examples show). But there isn't that I know of, so I wrote my own converter to do this. Note that the converter expects the object to have a getId()
method which returns a unique ID of some kind. If it doesn't it will fail. You can add logic to the getMethodName()
if you need to determine the name of the method to use as a key programmatically. Note that we use Seam in our project. If you don't use Seam, the NO_SELECTION_VALUE parts can probably be removed as well as the three annotations on the class.
This code was inspired by: http://arjan-tijms.omnifaces.org/2011/12/automatic-to-object-conversion-in-jsf.html
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.faces.Converter;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
/**
* @author: Jason Wheeler
* @description Converter for lists (SelectOneMenu, SelectManyMenu, etc)
* @created: 09/05/2013
*/
@Name("listConverter")
@BypassInterceptors
@Converter
public class ListConverter implements javax.faces.convert.Converter {
private String NO_SELECTION_VALUE = "org.jboss.seam.ui.NoSelectionConverter.noSelectionValue";
@Override
public String getAsString(FacesContext facesContext, UIComponent component, Object obj) {
if (obj == null) {
return NO_SELECTION_VALUE;
} else {
try {
Method method = obj.getClass().getMethod(getMethodName(obj));
return String.valueOf(method.invoke(obj));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public String getMethodName(Object obj) {
return "getId";
}
@Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String val) throws ConverterException {
if (val == null) {
return null;
} else if (val.equals(NO_SELECTION_VALUE)) {
return null;
} else {
for (SelectItem item : getSelectItems(component)) {
if (val.equals(getAsString(facesContext, component, item.getValue()))) {
return item.getValue();
}
}
return null;
}
}
protected Collection<SelectItem> getSelectItems(UIComponent component) {
Collection<SelectItem> collection = new ArrayList<SelectItem>();
for (UIComponent child : component.getChildren()) {
if (child instanceof UISelectItem) {
UISelectItem ui = (UISelectItem) child;
SelectItem item = (SelectItem) ui.getValue();
collection.add(item);
} else if (child instanceof UISelectItems) {
UISelectItems ui = (UISelectItems) child;
Object value = ui.getValue();
collection.addAll((Collection<SelectItem>) value);
}
}
return collection;
}
}
回答3:
I've just took @Bigwheels code, made some changes to work with JSF 2.0 and it fixed my problem:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import javax.faces.model.SelectItem;
import javax.persistence.Id;
@FacesConverter("selectItemConverter")
public class SelectItemConverter implements javax.faces.convert.Converter {
private String NO_SELECTION_VALUE = "SELECIONE";
@Override
public String getAsString(FacesContext facesContext, UIComponent component, Object obj) {
if (obj == null) {
return NO_SELECTION_VALUE;
} else {
try {
Method method = obj.getClass().getMethod(getIdMethodName(obj));
return String.valueOf(method.invoke(obj));
} catch (Exception e) {
throw new ConverterException(e);
}
}
}
public String getIdMethodName(Object obj) {
try {
Field[] fieldList = obj.getClass().getDeclaredFields();
Field id = null;
for (Field field : fieldList) {
if(field.isAnnotationPresent(Id.class)) {
id = field;
break;
}
}
return "get" + capitalize(id.getName());
} catch(Exception ex) {
throw new ConverterException(ex);
}
}
private String capitalize(final String line) {
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
}
@Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String val) throws ConverterException {
if (val == null) {
return null;
} else if (val.equals(NO_SELECTION_VALUE)) {
return null;
} else {
for (Object item : getSelectItems(component)) {
if (val.equals(getAsString(facesContext, component, item))) {
return item;
}
}
return null;
}
}
protected List getSelectItems(UIComponent component) {
List list = new ArrayList();
for (UIComponent child : component.getChildren()) {
if (child instanceof UISelectItem) {
UISelectItem ui = (UISelectItem) child;
SelectItem item = (SelectItem) ui.getValue();
list.add(item);
} else if (child instanceof UISelectItems) {
UISelectItems ui = (UISelectItems) child;
Object value = ui.getValue();
list.addAll((Collection<SelectItem>) value);
}
}
return list;
}
}
来源:https://stackoverflow.com/questions/16149109/hselectonemenu-generic-converter-for-all-entities-without-calling-db-again-and