Je crée une application Web dans laquelle vous devez lire une liste d'objets/entités d'une base de données et la renseigner dans un JSF <h:selectOneMenu>
. Je suis incapable de coder ceci. Est-ce que quelqu'un peut me montrer comment le faire?
Je sais comment obtenir un List<User>
à partir de la base de données. Ce que j'ai besoin de savoir, c'est comment renseigner cette liste dans un <h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Selon l'historique de vos questions, vous utilisez JSF 2.x. Alors, voici une réponse ciblée JSF 2.x. Dans JSF 1.x, vous seriez obligé d’envelopper les valeurs/étiquettes d’élément dans laid SelectItem
instances. Ce n'est heureusement plus nécessaire dans JSF 2.x.
Pour répondre directement à votre question, utilisez simplement <f:selectItems>
dont value
pointe vers une propriété List<T>
que vous conservez à partir de la base de données lors de la (post) construction du bean. Voici un exemple de base de lancement supposant que T
représente en réalité un String
.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{bean.names}" />
</h:selectOneMenu>
avec
@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)
}
Aussi simple que cela. En fait, la T
's toString()
sera utilisée pour représenter à la fois le libellé et la valeur de l'élément déroulant. Ainsi, lorsque vous utilisez une liste d'objets complexes comme List<String>
sans remplacer la méthode List<SomeEntity>
et que vous n'avez pas remplacé la méthode de la classe 'toString()
, vous voyez alors com.example.SomeEntity@hashcode
en tant que valeurs d'élément. Voir la section suivante pour savoir comment le résoudre correctement.
Notez également que le bean pour la valeur <f:selectItems>
ne doit pas nécessairement être le même bean que le bean pour la valeur <h:selectOneMenu>
. Ceci est utile lorsque les valeurs sont en fait des constantes à l'échelle de l'application que vous ne devez charger qu'une seule fois au démarrage de l'application. Vous pouvez ensuite simplement en faire une propriété d'un haricot d'application.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Lorsque T
concerne un objet complexe (un javabean), tel que User
qui a une propriété String
de name
, vous pouvez utiliser l'attribut var
pour obtenir la variable d'itération que vous pouvez utiliser à son tour dans itemValue
et/ou itemLabel
attribtues ( si vous omettez la itemLabel
, alors l'étiquette devient identique à la valeur).
Exemple 1:
<h:selectOneMenu value="#{bean.userName}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>
avec
private String userName;
private List<User> users;
@EJB
private UserService userService;
@PostConstruct
public void init() {
users = userService.list();
}
// ... (getters, setters, etc)
Ou s'il a une propriété Long
id
que vous préféreriez définir comme valeur d'élément:
Exemple n ° 2:
<h:selectOneMenu value="#{bean.userId}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>
avec
private Long userId;
private List<User> users;
// ... (the same as in previous bean example)
Chaque fois que vous souhaitez définir une propriété T
dans le bean également et que T
représente une User
, vous devez créer un paramètre Converter
qui convertit entre User
et une représentation sous forme de chaîne unique (pouvant être le id
propriété). Notez que itemValue
doit représenter l'objet complexe lui-même, exactement le type qui doit être défini en tant que value
du composant de sélection.
<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
avec
private User user;
private List<User> users;
// ... (the same as in previous bean example)
et
@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);
}
}
}
(notez s'il vous plaît que la Converter
est un peu compliquée pour pouvoir injecter un @EJB
dans un convertisseur JSF; normalement, on l'aurait annoté comme @FacesConverter(forClass=User.class)
, mais cela ne permet malheureusement pas l'injection de @EJB
)
N'oubliez pas de vous assurer que la classe d'objets complexes a equals()
et hashCode()
correctement implémentée , sinon, JSF ne pourra pas, lors du rendu, afficher les éléments présélectionnés et vous ferez face à Erreur de validation: valeur n'est pas valide .
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();
}
}
Head to this answer: Implémenter des convertisseurs pour des entités avec Java Generics .
La bibliothèque d’utilitaires JSF - OmniFaces propose un convertisseur spécial prêt à l’emploi qui vous permet d’utiliser des objets complexes dans <h:selectOneMenu>
sans qu’il soit nécessaire de créer un convertisseur personnalisé. Le SelectItemsConverter
effectuera simplement la conversion en fonction des éléments facilement disponibles dans <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>
Voir page
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
<f:selectItems value="#{page.names}"/>
</h:selectOneMenu>
Haricot
List<SelectItem> names = new ArrayList<SelectItem>();
//-- Populate list from database
names.add(new SelectItem(valueObject,"label"));
//-- setter/getter accessor methods for list
Pour afficher un enregistrement particulier sélectionné, il doit s'agir d'une des valeurs de la liste.
Le Balusc donne une réponse très utile sur ce sujet. Mais il ne présente pas une alternative: le convertisseur générique Roll-your-own qui gère les objets complexes en tant qu’élément sélectionné. C'est très complexe à faire si vous voulez gérer tous les cas, mais assez simple pour les cas simples.
Le code ci-dessous contient un exemple d'un tel convertisseur. Il fonctionne dans le même esprit que OmniFaces SelectItemsConverter , dans la mesure où il regarde à travers les enfants d’un composant les objets contenant UISelectItem(s)
. La différence est qu’il ne gère que les liaisons à des collections simples d’objets d’entité ou à des chaînes. Il ne gère pas les groupes d'éléments, les collections de SelectItem
s, les tableaux et probablement beaucoup d'autres choses.
Les entités auxquelles le composant se lie doivent implémenter l'interface IdObject
. (Cela pourrait être résolu autrement, par exemple en utilisant toString
.)
Notez que les entités doivent implémenter equals
de manière à ce que deux entités portant le même ID se comparent de manière égale.
La seule chose à faire pour l'utiliser consiste à le spécifier en tant que convertisseur sur le composant select, à le lier à une propriété d'entité et à une liste d'entités possibles:
<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>
Convertisseur:
/**
* 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");
}
}
Appelez-moi paresseux, mais coder un convertisseur semble un travail inutile. J'utilise Primefaces et, n'ayant pas utilisé auparavant de liste déroulante ou de menu déroulant Vanilla JSF2, j'ai simplement supposé (paresseux) que le widget pouvait gérer des objets complexes, c'est-à-dire transmettre l'objet sélectionné tel quel à son getter/setter correspondant comme tel. beaucoup d'autres widgets font. J'ai été déçu de constater (après des heures de casse-tête) que cette fonctionnalité n'existe pas pour ce type de widget sans convertisseur. En fait, si vous fournissez un objet de définition pour l'objet complexe plutôt que pour une chaîne, il échouera en silence (n'appelle simplement pas l'utilisateur, pas d'exception, ni d'erreur JS), et j'ai passé une tonne de temps à parcourir l'excellent BalusC outil de dépannage pour trouver la cause, mais en vain puisque aucune de ces suggestions ne s’applique. Ma conclusion: le widget listbox/menu doit être adapté aux autres widgets JSF2. Cela semble trompeur et susceptible de conduire le développeur non informé comme moi dans un terrier de lapin.
En fin de compte, j’ai résisté à la codification d’un convertisseur et constaté, par essais et erreurs, que si vous définissez la valeur du widget sur un objet complexe, par exemple:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... lorsque l'utilisateur sélectionne un élément, le widget peut appeler un outil de définition de chaîne pour cet objet, par exemple. setSelectedThing(String thingString) {...}
, et la chaîne transmise est une chaîne JSON représentant l'objet Thing. Je peux l'analyser pour déterminer quel objet a été sélectionné. Cela ressemble un peu à un hack, mais moins d'un hack qu'un convertisseur.
Je le fais comme ça:
Les modèles sont ViewScoped
convertisseur:
@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);
}
}
et lier au composant avec:
<f:converter binding="#{viewScopedFacesConverter}" />
Si vous utilisez un identifiant d'entité plutôt que hashCode, vous pouvez provoquer une collision si vous avez peu de listes sur une page pour différentes entités (classes) avec le même identifiant