web-dev-qa-db-fra.com

Comment renseigner les options de h: selectOneMenu à partir de la base de données?

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>
67
Illep

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.


Exemple de base

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>

Objets complexes comme objets disponibles

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é Longid 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)

Objet complexe en tant qu'élément sélectionné

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

}

Objets complexes avec un convertisseur générique

Head to this answer: Implémenter des convertisseurs pour des entités avec Java Generics .


Objets complexes sans convertisseur personnalisé

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 également:

166
BalusC

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.

8
Nayan Wadekar

Convertisseur générique à rouler pour des objets complexes en tant qu'élément sélectionné

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 SelectItems, 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");
  }

}
3
Lii

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.

0
snakedog

Je le fais comme ça:

  1. Les modèles sont ViewScoped

  2. 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

0
user2512686