79 votes

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

Je suis en train de créer une application web, où il faut lire une liste d'objets/entités à partir d'une base de données et la remplir dans un JSF. <h:selectOneMenu> . Je suis incapable de coder cela. Quelqu'un peut-il me montrer comment le faire ?

Je sais comment obtenir un List<User> de la DB. Ce que j'ai besoin de savoir, c'est comment alimenter cette liste dans une base de données. <h:selectOneMenu> .

<h:selectOneMenu value="#{bean.name}">
    ...?
</h:selectOneMenu>

187voto

BalusC Points 498232

D'après l'historique de vos questions, vous utilisez JSF 2.x. Voici donc une réponse adaptée à JSF 2.x. Dans JSF 1.x, vous seriez contraint d'envelopper les valeurs/étiquettes des éléments dans d'affreux fichiers SelectItem instances. Heureusement, cela n'est plus nécessaire dans JSF 2.x.


Exemple de base

Pour répondre directement à votre question, il suffit d'utiliser <f:selectItems> dont value pointe vers un List<T> propriété que vous préservez de la DB pendant la (post)construction du haricot. Voici un exemple de démarrage de base en supposant que T représente en fait 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)
}

C'est aussi simple que cela. En fait, le T 's toString() sera utilisé pour représenter à la fois le libellé et la valeur de l'élément de la liste déroulante. Ainsi, lorsque vous êtes au lieu de List<String> en utilisant une liste d'objets complexes comme List<SomeEntity> et que vous n'avez pas surchargé la classe toString() vous verrez alors com.example.SomeEntity@hashcode comme valeurs d'éléments. Voir la section suivante pour savoir comment le résoudre correctement.

Notez également que le haricot pour <f:selectItems> ne doit pas nécessairement être le même bean que le bean pour <h:selectOneMenu> valeur. Ceci est utile lorsque les valeurs sont en fait des constantes de l'application que vous n'avez à charger qu'une seule fois pendant le démarrage de l'application. Vous pouvez alors simplement en faire une propriété d'un bean à portée applicative.

<h:selectOneMenu value="#{bean.name}">
    <f:selectItems value="#{data.names}" />
</h:selectOneMenu>

Objets complexes comme éléments disponibles

Chaque fois que T concerne un objet complexe (un javabean), tel que User qui a un String propriété de name alors vous pouvez utiliser l'option var pour obtenir la variable d'itération que vous pouvez utiliser à votre tour dans itemValue et/ou itemLabel attributs (si vous omettez le itemLabel alors l'étiquette devient la même que la valeur).

Exemple n° 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 quand il a un Long propriété id que vous souhaitez plutôt 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 comme élément sélectionné

Chaque fois que vous voulez le mettre sur un T dans le haricot ainsi que T représente un User vous devez alors créer un fichier personnalisé Converter qui convertit entre User et une représentation unique sous forme de chaîne de caractères (qui peut être l' id propriété). Veuillez noter que le itemValue doit représenter l'objet complexe lui-même, exactement le type qui doit être défini en tant qu'élément de sélection du composant value .

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

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

}

(veuillez noter que le Converter est un peu bricolé pour pouvoir injecter une @EJB dans un convertisseur JSF ; normalement, on l'aurait annoté en tant que @FacesConverter(forClass=User.class) , mais cela ne permet malheureusement pas @EJB injections )

N'oubliez pas de vous assurer que la classe d'objets complexes possède equals() y hashCode() correctement mis en œuvre sinon, lors du rendu, JSF n'affichera pas les éléments présélectionnés, et vous serez confronté à l'échec de l'opération. Erreur de validation : La 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

Dirigez-vous vers cette réponse : Implémenter des convertisseurs pour les entités avec Java Generics .


Objets complexes sans convertisseur personnalisé

La bibliothèque utilitaire JSF OmniFaces offre un convertisseur spécial qui vous permet d'utiliser des objets complexes dans les applications de l'UE. <h:selectOneMenu> sans qu'il soit nécessaire de créer un convertisseur personnalisé. Le site SelectItemsConverter effectuera simplement la conversion en se basant sur des éléments facilement disponibles dans les pays de l'UE. <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 aussi :

11voto

Nayan Wadekar Points 5580

Voir-Page

<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

Pour afficher un enregistrement particulier sélectionné, celui-ci doit être l'une des valeurs de la liste.

3voto

Lii Points 690

Convertisseur générique pour les objets complexes en tant qu'élément sélectionné.

Le Balusc donne une réponse générale très utile sur ce sujet. Mais il y a une alternative qu'il ne présente pas : Le convertisseur générique Roll-your-own qui gère les objets complexes comme l'élément sélectionné. C'est très complexe à faire si on veut 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 le convertisseur OmniFaces SelectItemsConverter en recherchant dans les enfants d'un composant les éléments suivants UISelectItem(s) contenant des objets. La différence est qu'il ne gère que les liaisons avec des collections simples d'objets d'entités ou avec des chaînes de caractères. Il ne gère pas les groupes d'éléments, les collections d'objets SelectItem s, les tableaux et probablement beaucoup d'autres choses.

Les entités auxquelles le composant se lie doivent mettre en œuvre l'attribut IdObject l'interface. (Ce problème pourrait être résolu d'une autre manière, par exemple en utilisant l'interface toString .)

Notez que les entités doivent implémenter equals de telle sorte que deux entités ayant le même ID se comparent de manière égale.

La seule chose que vous devez faire pour l'utiliser est de le spécifier comme convertisseur sur le composant de sélection, de 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");
  }

}

0voto

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 le composant avec :

 <f:converter binding="#{viewScopedFacesConverter}" />

Si vous utilisez l'identifiant de l'entité plutôt que le code de hachage, vous risquez une collision si vous avez plusieurs listes sur une page pour différentes entités (classes) avec le même identifiant.

0voto

snakedog Points 1

Je suis peut-être paresseux, mais coder un convertisseur me semble être un travail inutile. J'utilise Primefaces et, n'ayant jamais utilisé une simple boîte de liste ou un menu déroulant JSF2, j'ai supposé (étant paresseux) que le widget pouvait gérer des objets complexes, c'est-à-dire passer l'objet sélectionné tel quel à son getter/setter correspondant comme le font tant d'autres widgets. J'ai été déçu de constater (après des heures de grattage de tête) que cette capacité n'existe pas pour ce type de widget sans un convertisseur. En fait, si vous fournissez un setter pour l'objet complexe plutôt que pour une chaîne, il échoue silencieusement (il n'appelle simplement pas le setter, pas d'exception, pas d'erreur JS), et j'ai passé une tonne de temps à parcourir les pages suivantes L'excellent outil de dépannage de BalusC pour trouver la cause, en vain puisque aucune de ces suggestions ne s'appliquait. Ma conclusion : le widget listbox/menu nécessite une adaptation que les autres widgets JSF2 n'ont pas. Cela semble trompeur et susceptible d'entraîner le développeur non averti comme moi dans un trou de lapin.

Finalement, j'ai résisté à l'idée de coder un convertisseur et j'ai découvert par essais et erreurs que si vous définissez la valeur du widget sur un objet complexe, par ex :

<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">

... lorsque l'utilisateur sélectionne un élément, le widget peut appeler un setter String pour cet objet, par ex. setSelectedThing(String thingString) {...} et la chaîne transmise est une chaîne JSON représentant l'objet Chose. Je peux l'analyser pour déterminer quel objet a été sélectionné. Cela ressemble un peu à un hack, mais moins à un hack qu'à un convertisseur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X