Wie fülle ich Optionen von h: selectOneMenu aus der Datenbank aus?

72

Ich erstelle eine Webanwendung, in der Sie eine Liste von Objekten / Entitäten aus einer Datenbank lesen und in eine JSF einfügen müssen <h:selectOneMenu>. Ich kann das nicht codieren. Kann mir jemand zeigen, wie es geht?

Ich weiß, wie man einen List<User>von der DB bekommt . Was ich wissen muss ist, wie man diese Liste in a füllt <h:selectOneMenu>.

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

Antworten:

180

Basierend auf Ihrem Fragenverlauf verwenden Sie JSF 2.x. Hier ist eine gezielte Antwort auf JSF 2.x. In JSF 1.x wären Sie gezwungen, Elementwerte / Beschriftungen in hässlichen SelectItemFällen zu verpacken . Dies wird in JSF 2.x glücklicherweise nicht mehr benötigt.


Grundlegendes Beispiel

Um Ihre Frage direkt zu beantworten, verwenden Sie einfach <f:selectItems>deren valuePunkte für eine List<T>Eigenschaft, die Sie während der (Post-) Konstruktion von Bean aus der DB beibehalten. Hier ist ein grundlegendes Kickoff-Beispiel unter der Annahme, dass es sich Ttatsächlich um a handelt String.

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

mit

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

So einfach ist das. Tatsächlich werden die T's toString()verwendet, um sowohl die Dropdown-Elementbezeichnung als auch den Wert darzustellen. Wenn Sie also List<String>keine Liste komplexer Objekte wie verwenden List<SomeEntity>und die toString()Methode der Klasse nicht überschrieben haben , werden sie com.example.SomeEntity@hashcodeals Elementwerte angezeigt. Siehe nächster Abschnitt, wie man es richtig löst.

Beachten Sie auch, dass die Bean für den <f:selectItems>Wert nicht unbedingt dieselbe Bean sein muss wie die Bean für den <h:selectOneMenu>Wert. Dies ist immer dann nützlich, wenn es sich bei den Werten tatsächlich um anwendungsweite Konstanten handelt, die Sie beim Start der Anwendung nur einmal laden müssen. Sie können es dann einfach zu einer Eigenschaft einer Bean mit Anwendungsbereich machen.

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

Komplexe Objekte als verfügbare Elemente

Wenn es sich Tum ein komplexes Objekt (eine Javabean) handelt, das beispielsweise Userdie StringEigenschaft hat name, können Sie das varAttribut verwenden, um die Iterationsvariable abzurufen, die Sie wiederum in itemValueund / oder itemLabelattribtues verwenden können (wenn Sie die weglassen itemLabel, dann die label wird gleich dem Wert).

Beispiel 1:

<h:selectOneMenu value="#{bean.userName}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>

mit

private String userName;
private List<User> users;

@EJB
private UserService userService;

@PostConstruct
public void init() {
    users = userService.list();
}

// ... (getters, setters, etc)

Oder wenn es eine LongEigenschaft hat, iddie Sie lieber als Elementwert festlegen möchten:

Beispiel 2:

<h:selectOneMenu value="#{bean.userId}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>

mit

private Long userId;
private List<User> users;

// ... (the same as in previous bean example)

Komplexes Objekt als ausgewähltes Element

Wann immer Sie es auch auf eine TEigenschaft in der Bean setzen möchten und Teine darstellen Usermöchten, müssen Sie einen benutzerdefinierten ConverterWert backen , der zwischen Usereiner eindeutigen Zeichenfolgendarstellung konvertiert (die die idEigenschaft sein kann). Beachten Sie, dass das itemValuemuss das komplexe Objekt selbst darstellen, genau den Typ, der als Auswahlkomponente festgelegt werden muss value.

<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

mit

private User user;
private List<User> users;

// ... (the same as in previous bean example)

und

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

}

(Bitte beachten Sie, dass das Converteretwas hackig ist, um einen @EJBin einen JSF-Konverter einspeisen zu können ; normalerweise hätte man es als kommentiert @FacesConverter(forClass=User.class), aber das erlaubt leider keine @EJBEinspritzungen )

Vergessen Sie nicht , um sicher zu stellen , dass die komplexe Objektklasse hat equals()und hashCode()ordnungsgemäß umgesetzt werden , sonst JSF wird während machen nicht vorher ausgewählte Artikel zeigen (s), und Sie werden auf Gesicht einreichen Validierungsfehler: Wert nicht gültig ist .

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

}

Komplexe Objekte mit einem generischen Konverter

Gehen Sie zu dieser Antwort: Implementieren Sie Konverter für Entitäten mit Java Generics .


Komplexe Objekte ohne benutzerdefinierten Konverter

Die JSF-Dienstprogrammbibliothek OmniFaces bietet einen speziellen Konverter, mit dem Sie komplexe Objekte verwenden können, <h:selectOneMenu>ohne einen benutzerdefinierten Konverter erstellen zu müssen. Der SelectItemsConverterwird einfach die Konvertierung basierend auf leicht verfügbaren Artikeln in durchführen <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>

Siehe auch:

BalusC
quelle
Sehr gute Erklärung! Konverter, die keine Einspritzziele sind, sind in JSF eine problematische Aufgabe. Ich glaube, dass eine tragbare Naht 3-Erweiterung dies beheben wird. Es ist auch auf der Roadmap für JSF 2.2 / Java EE 7.
Mike Braun
@ Makky: Ich habe keine Ahnung, wovon du sprichst. Ich initialisiere die Liste nirgends explizit selbst. In diesem Beispiel wird ein Standard-Java-EE-Stapel mit EJB und JPA angenommen. Dies scheint auch nicht themenbezogen zu sein. Drücken Sie Ask Question, um eine klare und konkrete Frage zu stellen.
BalusC
Hallo BalusC, ich habe eine Frage gestellt. Könnten
Makky
Warum sind diese Anmerkungsbezeichnungen in Ihren Beispielen enthalten? Soll ich Anmerkungen verwenden? Warum?
Mushy
1
@ZiMtyth Abschnitt "Komplexes Objekt als ausgewähltes Element" von oben nach unten lesen. Für zukünftige "nichts passiert" Probleme gehen Sie zu stackoverflow.com/q/2118656
BalusC
8

Ansichtsseite

<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

Um einen bestimmten ausgewählten Datensatz anzuzeigen, muss er einer der Werte in der Liste sein.

Nayan Wadekar
quelle
Was ist valueObject in Ihrem Code? Ist es die Liste der Datensätze, die in der Dropdown-Liste aufgeführt werden?
Illep
Dies ist das Objekt, das bei Auswahl einer der Optionen (hier selectedName) in der Bean festgelegt wird. value (valueObject) kann eine ID sein, die Sie im Back-End haben möchten, muss jedoch den Namen mithilfe von label auf dem Bildschirm anzeigen.
Nayan Wadekar
3

Roll-your-own generischer Konverter für komplexe Objekte als ausgewähltes Element

Der Balusc gibt eine sehr nützliche Übersichtsantwort zu diesem Thema. Es gibt jedoch eine Alternative, die er nicht vorstellt: den generischen Roll-Your-Own-Konverter, der komplexe Objekte als ausgewähltes Element behandelt. Dies ist sehr komplex, wenn Sie alle Fälle behandeln möchten, aber für einfache Fälle ziemlich einfach.

Der folgende Code enthält ein Beispiel für einen solchen Konverter. Es funktioniert im gleichen Sinne wie der OmniFaces SelectItemsConverter, wenn es die untergeordneten Elemente einer Komponente nach UISelectItem(s)Objekten durchsucht . Der Unterschied besteht darin, dass nur Bindungen an einfache Sammlungen von Entitätsobjekten oder an Zeichenfolgen verarbeitet werden. Es behandelt keine Elementgruppen, Sammlungen von SelectItems, Arrays und wahrscheinlich viele andere Dinge.

Die Entitäten, an die die Komponente gebunden ist, müssen die IdObjectSchnittstelle implementieren . (Dies könnte auf andere Weise gelöst werden, z. B. durch Verwendung von toString.)

Beachten Sie, dass die Entitäten so implementiert werden müssen equals, dass zwei Entitäten mit derselben ID gleich sind.

Das einzige, was Sie tun müssen, um es zu verwenden, ist, es als Konverter für die Auswahlkomponente anzugeben, an eine Entitätseigenschaft und eine Liste möglicher Entitäten zu binden:

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

Konverter:

/**
 * 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");
  }

}
Lii
quelle
0

Ich mache es so:

  1. Modelle sind ViewScoped

  2. Konverter:

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

und binden an Komponente mit:

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

Wenn Sie die Entitäts-ID anstelle von hashCode verwenden, können Sie eine Kollision auslösen. Wenn Sie auf einer Seite nur wenige Listen für verschiedene Entitäten (Klassen) mit derselben ID haben


quelle
0

Nennen Sie mich faul, aber das Codieren eines Konverters scheint eine Menge unnötiger Arbeit zu sein. Ich verwende Primefaces und habe, nachdem ich zuvor kein einfaches Vanille-JSF2-Listenfeld oder Dropdown-Menü verwendet hatte, nur angenommen (faul), dass das Widget komplexe Objekte verarbeiten kann, dh das ausgewählte Objekt so wie es ist an den entsprechenden Getter / Setter weitergibt viele andere Widgets tun dies. Ich war enttäuscht (nach stundenlangem Kopfkratzen) festzustellen, dass diese Funktion für diesen Widget-Typ ohne Konverter nicht vorhanden ist. Wenn Sie einen Setter für das komplexe Objekt und nicht für einen String angeben, schlägt dies stillschweigend fehl (der Setter wird einfach nicht aufgerufen, keine Ausnahme, kein JS-Fehler), und ich habe eine Menge Zeit damit verbracht, BalusCs hervorragendes Tool zur Fehlerbehebung durchzugehendie Ursache zu finden, ohne Erfolg, da keiner dieser Vorschläge zutraf. Mein Fazit: Das Listenfeld- / Menü-Widget muss angepasst werden, was andere JSF2-Widgets nicht tun. Dies scheint irreführend und neigt dazu, den nicht informierten Entwickler wie mich in ein Kaninchenloch zu führen.

Am Ende habe ich mich geweigert, einen Konverter zu codieren, und durch Ausprobieren festgestellt, dass, wenn Sie den Widget-Wert auf ein komplexes Objekt setzen, z.

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

... wenn der Benutzer ein Element auswählt, kann das Widget beispielsweise einen String-Setter für dieses Objekt aufrufen setSelectedThing(String thingString) {...}, und der übergebene String ist ein JSON-String, der das Thing-Objekt darstellt. Ich kann es analysieren, um festzustellen, welches Objekt ausgewählt wurde. Dies fühlt sich ein wenig wie ein Hack an, ist aber weniger ein Hack als ein Converter.

Schlangenhund
quelle
Was meinen Sie mit "Fazit: Das Listenfeld- / Menü-Widget muss angepasst werden als andere JSF2-Widgets nicht." ? Die Tatsache, dass es Konverter braucht? Eine Ebene h:inputTextfunktioniert auch, wenn Sie Ihre eigenen stark typisierten Objekte erstellt haben, anstatt eine Zeichenfolge zu verwenden.
Kukeltje
Ich meine, dass andere Widgets komplexe Objekte verarbeiten können (von der Backing Bean übergeben und auf diese gesetzt werden), während Listbox / Menü dies nicht können. Wenn man darüber nachdenkt, ist String tatsächlich ein komplexes Objekt, so dass man denken würde, dass diese Widgets in der Lage wären, jedes komplexe Objekt zu handhaben. Es scheint mir, dass h:inputTextoder sogar seine anspruchsvolleren Geschwister p:inputTextStrings von Natur aus behandeln. listbox / menu scheint in der Lage zu sein, jedes Objekt zu verarbeiten, obwohl dieses Objekt natürlich nur durch einen String in der Benutzeroberfläche dargestellt werden kann.
Snakedog
h:inputTextund p:inputTextkann auch mit Zahlen und mehr umgehen. Dies liegt daran, dass diese auf bekannten Java-Typen basieren und Konverter von jsf bereitgestellt und implizit angewendet werden. Für jeden anderen Typ wird ebenfalls ein Konverter benötigt, z. B. eine benutzerdefinierte, stark typisierte GTIN , die effektiv eine Zahl ist (in dieser Implementierung wird ein String verwendet). Und ja, die Konvertierung zur und von der 'clientseitigen' String-Darstellung ist das, worum sich Konverter kümmern ...
Kukeltje
Was Sie codiert haben, klingt wie ein Serializer / Deserializer, der letztendlich als Konverter fungiert (der sogar für clientseitige Manipulationen anfällig sein kann). Ich vermute, Sie haben dies entweder in der Entität oder in der Backing Bean (Controller) codiert, die beide keine Kenntnis von dieser "Konvertierungssache" zwischen Client und Server haben sollten, daher klingt es für mich eher nach einem Hack. Vor allem , da Omnifaces hat Dinge wie showcase.omnifaces.org/converters/SelectItemsConverter . Und denken Sie daran, dass Sie auch die integrierten Konverter verwenden, wenn Sie ausgewählte Menüs haben, die mit Listen von Zeichenfolgen arbeiten.
Kukeltje
Das ist eine ganz andere Perspektive, danke. Als Primefaces-Benutzer habe ich erwartet, dass diese Widgets "einfach funktionieren" wie andere Widgets, die ich verwendet habe. Ich wusste nicht, dass einige JSF-Widgets integrierte Konverter haben und andere nicht. In Zukunft werde ich eingebaute Konverter als eine Annehmlichkeit betrachten, die mir das Framework bietet, und nicht als eine erwartete Funktion. Ich weiß jedoch nicht, dass sich das Bereitstellen der Konvertierung im Getter / Setter wesentlich von dem Bereitstellen in einer separaten Klasse unterscheidet, und es scheint einfacher zu sein.
Snakedog