database - llenar - selectonemenu jsf dinamico
Cómo llenar las opciones de h: selectOneMenu de la base de datos? (5)
Estoy creando una aplicación web, donde debe leer una lista de objetos / entidades de un DB y <h:selectOneMenu>
en un JSF <h:selectOneMenu>
. No puedo codificar esto. ¿Puede alguien mostrarme cómo hacerlo?
Sé cómo obtener una List<User>
de la base de datos. Lo que necesito saber es cómo llenar esta lista en a <h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Transforma tu convertidor genérico para objetos complejos como elemento seleccionado
El Balusc da una respuesta general muy útil sobre este tema. Pero hay una alternativa que no presenta: el convertidor genérico Roll-your-own que maneja objetos complejos como el elemento seleccionado. Esto es muy complejo de hacer si quiere manejar todos los casos, pero es bastante simple para casos simples.
El siguiente código contiene un ejemplo de dicho convertidor. Funciona con el mismo espíritu que OmniFaces SelectItemsConverter mientras se ve a través de los elementos UISelectItem(s)
de un componente para UISelectItem(s)
contienen objetos. La diferencia es que solo maneja enlaces a colecciones simples de objetos de entidad o cadenas. No maneja grupos de elementos, colecciones de SelectItem
, matrices y probablemente muchas otras cosas.
Las entidades a las que se une el componente deben implementar la interfaz IdObject
. (Esto podría resolverse de otra manera, como usar toString
).
Tenga en cuenta que las entidades deben implementar equals
de tal manera que dos entidades con el mismo ID se comparen por igual.
Lo único que debe hacer para usarlo es especificarlo como convertidor en el componente de selección, enlazar a una propiedad de entidad y una lista de entidades posibles:
<h:selectOneMenu value="#{bean.user}" converter="selectListConverter">
<f:selectItem itemValue="unselected" itemLabel="Select user..."/>
<f:selectItem itemValue="empty" itemLabel="No user"/>
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
Convertidor:
/**
* A converter for select components (those that have select items as children).
*
* It convertes the selected value string into one of its element entities, thus allowing
* binding to complex objects.
*
* It only handles simple uses of select components, in which the value is a simple list of
* entities. No ItemGroups, arrays or other kinds of values.
*
* Items it binds to can be strings or implementations of the {@link IdObject} interface.
*/
@FacesConverter("selectListConverter")
public class SelectListConverter implements Converter {
public static interface IdObject {
public String getDisplayId();
}
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
return component.getChildren().stream()
.flatMap(child -> getEntriesOfItem(child))
.filter(o -> value.equals(o instanceof IdObject ? ((IdObject) o).getDisplayId() : o))
.findAny().orElse(null);
}
/**
* Gets the values stored in a {@link UISelectItem} or a {@link UISelectItems}.
* For other components returns an empty stream.
*/
private Stream<?> getEntriesOfItem(UIComponent child) {
if (child instanceof UISelectItem) {
UISelectItem item = (UISelectItem) child;
if (!item.isNoSelectionOption()) {
return Stream.of(item.getValue());
}
} else if (child instanceof UISelectItems) {
Object value = ((UISelectItems) child).getValue();
if (value instanceof Collection) {
return ((Collection<?>) value).stream();
} else {
throw new IllegalStateException("Unsupported value of UISelectItems: " + value);
}
}
return Stream.empty();
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) return null;
if (value instanceof String) return (String) value;
if (value instanceof IdObject) return ((IdObject) value).getDisplayId();
throw new IllegalArgumentException("Unexpected value type");
}
}
Llámame flojo, pero codificar un convertidor parece mucho trabajo innecesario. Estoy usando Primefaces y, sin haber usado un list box JSF2 sencillo o un menú desplegable antes, asumí (siendo flojo) que el widget podía manejar objetos complejos, es decir, pasar el objeto seleccionado como está a su getter / setter correspondiente como tal muchos otros widgets lo hacen. Me decepcionó encontrar (después de horas de scratching) que esta capacidad no existe para este tipo de widget sin un convertidor. De hecho, si suministras un setter para el objeto complejo en lugar de String, falla silenciosamente (simplemente no llama al setter, no hay excepción, no hay error JS), y pasé mucho tiempo revisando la excelente herramienta de solución de problemas de BalusC para encontrar la causa, en vano ya que ninguna de esas sugerencias se aplica. Mi conclusión: widget de listbox / menú necesita adaptación que otros widgets JSF2 no tienen. Esto parece engañoso y propenso a llevar al desarrollador desinformado como yo por un agujero de conejo.
Al final, me resistí a codificar un convertidor y encontré a través de prueba y error que si estableces el valor del widget en un objeto complejo, por ejemplo:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... cuando el usuario selecciona un elemento, el widget puede llamar a un setter de cadenas para ese objeto, por ejemplo, setSelectedThing(String thingString) {...}
, y la cadena pasada es una cadena JSON que representa el objeto Thing. Puedo analizarlo para determinar qué objeto fue seleccionado. Esto se siente un poco como un hack, pero menos de un hack que un convertidor.
Lo estoy haciendo así:
Los modelos son ViewScoped
convertidor:
@Named @ViewScoped public class ViewScopedFacesConverter implements Converter, Serializable { private static final long serialVersionUID = 1L; private Map<String, Object> converterMap; @PostConstruct void postConstruct(){ converterMap = new HashMap<>(); } @Override public String getAsString(FacesContext context, UIComponent component, Object object) { String selectItemValue = String.valueOf( object.hashCode() ); converterMap.put( selectItemValue, object ); return selectItemValue; } @Override public Object getAsObject(FacesContext context, UIComponent component, String selectItemValue){ return converterMap.get(selectItemValue); } }
y enlazar al componente con:
<f:converter binding="#{viewScopedFacesConverter}" />
Si va a usar ID de entidad en lugar de hashCode, puede golpear una colisión, si tiene pocas listas en una página para diferentes entidades (clases) con la misma ID.
Según el historial de preguntas, estás usando JSF 2.x. Entonces, aquí hay una respuesta específica de JSF 2.x. En JSF 1.x, se vería obligado a ajustar los valores de los elementos en las feas instancias de SelectItem
. Afortunadamente, esto ya no es necesario en JSF 2.x.
Ejemplo básico
Para responder a su pregunta directamente, simplemente use <f:selectItems>
cuyo value
apunte a una propiedad de la List<T>
que conserva desde el DB durante la construcción (publicación) del bean. Aquí hay un ejemplo básico de inicio suponiendo que T
realidad representa una String
.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{bean.names}" />
</h:selectOneMenu>
con
@ManagedBean
@RequestScoped
public class Bean {
private String name;
private List<String> names;
@EJB
private NameService nameService;
@PostConstruct
public void init() {
names = nameService.list();
}
// ... (getters, setters, etc)
}
Simple como eso. En realidad, el T
''s toString()
se usará para representar tanto la etiqueta como el valor del elemento desplegable. Entonces, cuando estás en lugar de List<String>
usando una lista de objetos complejos como List<SomeEntity>
y no has reemplazado el método de la clase '' toString()
, entonces com.example.SomeEntity@hashcode
como item valores. Vea la siguiente sección cómo resolverlo correctamente.
También tenga en cuenta que el valor de bean for <f:selectItems>
no necesariamente tiene que ser el mismo bean que el bean para el valor de <h:selectOneMenu>
. Esto es útil cuando los valores son en realidad constantes de toda la aplicación que solo tiene que cargar una vez durante el inicio de la aplicación. Entonces podría convertirlo en propiedad de un bean con ámbito de aplicación.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Objetos complejos como artículos disponibles
Siempre que T
refiera a un objeto complejo (javabean), como User
que tiene una propiedad String
de name
, entonces podrías usar el atributo var
para obtener la variable de iteración que a su vez puedes usar en itemValue
y / o itemLabel
attribtues ( si omite el elemento itemLabel
, la etiqueta se convierte en el mismo que el valor).
Ejemplo 1:
<h:selectOneMenu value="#{bean.userName}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>
con
private String userName;
private List<User> users;
@EJB
private UserService userService;
@PostConstruct
public void init() {
users = userService.list();
}
// ... (getters, setters, etc)
O cuando tiene un id
propiedad Long
que le gustaría establecer como valor de artículo:
Ejemplo # 2:
<h:selectOneMenu value="#{bean.userId}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>
con
private Long userId;
private List<User> users;
// ... (the same as in previous bean example)
Objeto complejo como elemento seleccionado
Siempre que desee establecerlo en una propiedad T
en el bean y T
represente un User
, entonces deberá hornear un Converter
personalizado que convierta entre el User
y una representación de cadena única (que puede ser la propiedad del id
). Tenga en cuenta que itemValue
debe representar el objeto complejo en sí mismo, exactamente el tipo que debe establecerse como el value
del componente de selección.
<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
con
private User user;
private List<User> users;
// ... (the same as in previous bean example)
y
@ManagedBean
@RequestScoped
public class UserConverter implements Converter {
@EJB
private UserService userService;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return userService.find(Long.valueOf(submittedValue));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User ID", submittedValue)), e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof User) {
return String.valueOf(((User) modelValue).getId());
} else {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
}
}
}
(tenga en cuenta que el Converter
es un poco raro para poder inyectar un @EJB
en un convertidor JSF, normalmente uno lo habría anotado como @FacesConverter(forClass=User.class)
, pero eso lamentablemente no permite @EJB
Inyecciones de @EJB
)
No se olvide de asegurarse de que la clase de objeto complejo tenga equals()
y hashCode()
correctamente implementados , de lo contrario JSF durante el procesamiento no podrá mostrar los elementos preseleccionados, y usted en la cara del envío Error de validación: El valor no es válido
public class User {
private Long id;
@Override
public boolean equals(Object other) {
return (other != null && getClass() == other.getClass() && id != null)
? id.equals(((User) other).id)
: (other == this);
}
@Override
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}
}
Objetos complejos con un convertidor genérico
Dirígete a esta respuesta: Implementa convertidores para entidades con Java Generics .
Objetos complejos sin un convertidor personalizado
La biblioteca de utilidades JSF OmniFaces ofrece un conversor especial de la caja que le permite usar objetos complejos en <h:selectOneMenu>
sin la necesidad de crear un convertidor personalizado. SelectItemsConverter
simplemente realizará la conversión en función de los elementos fácilmente disponibles en <f:selectItem(s)>
.
<h:selectOneMenu value="#{bean.user}" converter="omnifaces.SelectItemsConverter">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
Ver también:
- Nuestra página wiki
<h:selectOneMenu>
Ver pagina
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
<f:selectItems value="#{page.names}"/>
</h:selectOneMenu>
Backing-Bean
List<SelectItem> names = new ArrayList<SelectItem>();
//-- Populate list from database
names.add(new SelectItem(valueObject,"label"));
//-- setter/getter accessor methods for list
Para mostrar un registro seleccionado en particular, debe ser uno de los valores en la lista.