How to populate options of h:selectOneMenu from database?
Asked Answered
B

5

79

I am creating a web application, where you have to read a list of objects / entities from a DB and populate it in a JSF <h:selectOneMenu>. I am unable to code this. Can someone show me how to do it?

I know how to get a List<User> from the DB. What I need to know is, how to populate this list in a <h:selectOneMenu>.

<h:selectOneMenu value="#{bean.name}">
    ...?
</h:selectOneMenu>
Baltazar answered 27/7, 2011 at 17:58 Comment(0)
S
188

Based on your question history, you're using JSF 2.x. So, here's a JSF 2.x targeted answer. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem instances. This is fortunately not needed anymore in JSF 2.x.


Basic example

To answer your question directly, just use <f:selectItems> whose value points to a List<T> property which you preserve from the DB during bean's (post)construction. Here's a basic kickoff example assuming that T actually represents a String.

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

with

@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 as that. Actually, the T's toString() will be used to represent both the dropdown item label and value. So, when you're instead of List<String> using a list of complex objects like List<SomeEntity> and you haven't overridden the class' toString() method, then you would see com.example.SomeEntity@hashcode as item values. See next section how to solve it properly.

Also note that the bean for <f:selectItems> value does not necessarily need to be the same bean as the bean for <h:selectOneMenu> value. This is useful whenever the values are actually applicationwide constants which you just have to load only once during application's startup. You could then just make it a property of an application scoped bean.

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

Complex objects as available items

Whenever T concerns a complex object (a javabean), such as User which has a String property of name, then you could use the var attribute to get hold of the iteration variable which you in turn can use in itemValue and/or itemLabel attribtues (if you omit the itemLabel, then the label becomes the same as the value).

Example #1:

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

with

private String userName;
private List<User> users;

@EJB
private UserService userService;

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

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

Or when it has a Long property id which you would rather like to set as item value:

Example #2:

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

with

private Long userId;
private List<User> users;

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

Complex object as selected item

Whenever you would like to set it to a T property in the bean as well and T represents an User, then you would need to bake a custom Converter which converts between User and an unique string representation (which can be the id property). Do note that the itemValue must represent the complex object itself, exactly the type which needs to be set as selection component's value.

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

with

private User user;
private List<User> users;

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

and

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

}

(please note that the Converter is a bit hacky in order to be able to inject an @EJB in a JSF converter; normally one would have annotated it as @FacesConverter(forClass=User.class), but that unfortunately doesn't allow @EJB injections)

Don't forget to make sure that the complex object class has equals() and hashCode() properly implemented, otherwise JSF will during render fail to show preselected item(s), and you'll on submit face Validation Error: Value is not valid.

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

}

Complex objects with a generic converter

Head to this answer: Implement converters for entities with Java Generics.


Complex objects without a custom converter

The JSF utility library OmniFaces offers a special converter out the box which allows you to use complex objects in <h:selectOneMenu> without the need to create a custom converter. The SelectItemsConverter will simply do the conversion based on readily available items in <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>

See also:

Sparse answered 27/7, 2011 at 20:15 Comment(16)
Very good explanation! Converters not being injection targets is a troublesome ommision in JSF. I believe a seam 3 portable extension will fix this. It's also on the roadmap for JSF 2.2/Java EE 7.Laise
@Makky: I have no idea what you're talking about. I am nowhere explicitly initializing the list myself. This example assumes standard Java EE stack with EJB and JPA. This doesn't seem to be on-topic to the question either. Press Ask Question to ask a clear and concrete question.Sparse
Hi BalusC I have raised a question could you please please help stackoverflow.com/questions/16062703/…Bowknot
Why are these annotation designations present within your examples? Should I used annotations? Why?Rubdown
@Mushy: unless explicitly otherwise mentioned, all my answers assume a stock Java EE environment to date. No changes should be necessary when you copy'n'paste'n'autocomplete'n'run the provided code snippets. You're of course free to change the annotations to fit your own environment. E.g. if you're using CDI to manage beans, then simply replace @ManagedBean by @Named, etc. This still doesn't require any changes in the demonstrated classes and methods.Sparse
@Sparse Thanks. We use Primefaces and in a situation to use converter. Can I still use converter="omnifaces.SelectItemsConverter" inside Primefaces?Scots
@Sparse thank you for the detailed and very clear explanation, it helped me understand it very well.Crasis
@Sparse I tried to pass user as itemValue to my bean, in mybean I instantiated a user in order to receive the user from the selectOneMenu, but I couldn't do that, nothing happens. I could do it in another way, I passed the id of the user to the bean and then did use Managed-Bean nameService to find the user, I have one question: can I understand that itemValue can only return primitive (by saying primitive I mean types included in Java by default) types such as String and Long and not any other possible Entity. please correct me if I'm wrong.Crasis
@ZiMtyth Read section "Complex object as selected item" from top to bottom. For future "nothing happens" issues, head to https://mcmap.net/q/17145/-commandbutton-commandlink-ajax-action-listener-method-not-invoked-or-input-value-not-set-updatedSparse
@Sparse I used what you suggested, it worked very well, except for one thing, I copy/paste the code of ConverterUser and a compilation error was generated, it's in this line: throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e); --> the error was: cannot find symbol symbol: variable e... i removed , e from it and everything works fine. is it ok to remove that part of code?Crasis
@Sparse I don't understand one more thing please, everything works fine without the instantiation of user, are not we supposed to have something like user = new User() to be possible to use this object? if no, why?Crasis
I did something similar in an application, for "Complex objects without a custom converter". I want to add an "User", and one of it's attributes is a "Role". So this <h:SelectOneMenu> is inside a <h:form>. When I try to submit, nothing happens, and in browser console I have the error: "Source map error: TypeError: NetworkError when attempting to fetch resource.". If I put in this <h:selectOneMenu> the ID of the role, and not the Role object (and remove the converter), the submit works fine.Ias
@CatalinVladu: make sure you display all validation and conversion messages. See #3 of https://mcmap.net/q/17145/-commandbutton-commandlink-ajax-action-listener-method-not-invoked-or-input-value-not-set-updated Then google the error message.Sparse
Found. Your answer for this question matches now.Ias
I would like to point out that it's generally a bad idea to expose the init() methods as public (like this example currently does). This exposes the internal logic of the class. @PostConstruct methods should generally be kept private.Coopery
@Adam: it will not anymore be inheritable and testable. If this is not a blocker for you, then just make it private. The world won't end either way.Sparse
P
11

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

To display particular selected record, it must be one of the values in the list.

Paintbrush answered 27/7, 2011 at 19:51 Comment(2)
what is valueObject in your code ? is it the List of records that will be listed in the dropdown ?Baltazar
Its the object that will be set in the bean upon selection of one of the options(here selectedName). value(valueObject) may be some id that you want at back-end, but have to display name on the screen using label.Paintbrush
R
3

Roll-your-own generic converter for complex objects as selected item

The Balusc gives a very useful overview answer on this subject. But there is one alternative he does not present: The Roll-your-own generic converter that handles complex objects as the selected item. This is very complex to do if you want to handle all cases, but pretty simple for simple cases.

The code below contains an example of such a converter. It works in the same spirit as the OmniFaces SelectItemsConverter as it looks through the children of a component for UISelectItem(s) containing objects. The difference is that it only handles bindings to either simple collections of entity objects, or to strings. It does not handle item groups, collections of SelectItems, arrays and probably a lot of other things.

The entities that the component binds to must implement the IdObject interface. (This could be solved in other way, such as using toString.)

Note that the entities must implement equals in such a way that two entities with the same ID compares equal.

The only thing that you need to do to use it is to specify it as converter on the select component, bind to an entity property and a list of possible entities:

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

Converter:

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

}
Reinaldo answered 18/10, 2014 at 11:37 Comment(0)
S
0

I'm doing it like this:

  1. Models are ViewScoped

  2. converter:

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

and bind to component with:

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

If you will use entity id rather than hashCode you can hit a collision- if you have few lists on one page for different entities (classes) with the same id

Savagism answered 3/10, 2017 at 16:18 Comment(0)
O
0

Call me lazy but coding a Converter seems like a lot of unnecessary work. I'm using Primefaces and, not having used a plain vanilla JSF2 listbox or dropdown menu before, I just assumed (being lazy) that the widget could handle complex objects, i.e. pass the selected object as is to its corresponding getter/setter like so many other widgets do. I was disappointed to find (after hours of head scratching) that this capability does not exist for this widget type without a Converter. In fact if you supply a setter for the complex object rather than for a String, it fails silently (simply doesn't call the setter, no Exception, no JS error), and I spent a ton of time going through BalusC's excellent troubleshooting tool to find the cause, to no avail since none of those suggestions applied. My conclusion: listbox/menu widget needs adapting that other JSF2 widgets do not. This seems misleading and prone to leading the uninformed developer like myself down a rabbit hole.

In the end I resisted coding a Converter and found through trial and error that if you set the widget value to a complex object, e.g.:

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

... when the user selects an item, the widget can call a String setter for that object, e.g. setSelectedThing(String thingString) {...}, and the String passed is a JSON String representing the Thing object. I can parse it to determine which object was selected. This feels a little like a hack, but less of a hack than a Converter.

Ozone answered 24/10, 2017 at 14:56 Comment(8)
What do you mean by "Conclusion: listbox/menu widget needs adapting than other JSF2 widgets do not." ? The fact that it needs converters? A plain h:inputText does too if you created your own strongly typed objects instead of using a string.Liaoning
I mean that other widgets can handle (get passed from and set on the backing bean) complex objects while listbox/menu cannot. On thinking about it, String is in fact a complex object, so one would think these widgets would be able to handle any complex object. It seems to me that h:inputText, or even its more sophisticated sibling p:inputText only handle Strings by their nature. listbox/menu seems like it should be able to handle any object, although of course that object can only be represented by a String in the UI.Ozone
h:inputText and p:inputText can handle numbers too and more. That is because these are based on known java types and converters are provided by jsf and implicitly applied. For any other type it needs a converter too, e.g. custom strongly typed GTIN that is effectively a number (in this implementation they use a String). And yes, the conversion to and from the 'client-side' String representation is what converters take care of...Liaoning
What you coded sounds like a serializer/deserializer that in the end acts as a converter (which might even be prone to client-side manipulation). I suspect you either coded this in the entity or in the backing bean (controller), both of which should have no knowledge of this 'converting thing' between client and server, so it sounds to me more like a hack. Especially since Omnifaces has things like showcase.omnifaces.org/converters/SelectItemsConverter. And keep in mind that if you have select menu's that operate on lists of Strings, you also use the built-in converters.Liaoning
That is a whole different perspective, thank you. As a Primefaces user I expected these widgets to "just work" like other widgets I have used. I did not know that some JSF widgets have built-in Converters and some don't. Going forward I shall regard built-in Converters as a convenience that the framework supplies to me rather than an expected feature. However, I don't know that providing the conversion in the getter/setter is materially different from providing it in a separate class, and it seems simpler.Ozone
No, it is not that some have built-in converters and others don't. All widgets call converters, most of them not directly but via the default mechanism in JSF. The fact that other widgets 'worked' seamlessly is because the type of the field bound to it is one that matches a default converter (converters can be configured to 'fire' for a specific type). Doing it in a getter/setter is complicating your logic and mixing in UI related logic making functionality less transparent. Creating a custom entity converter that runs on each of your entities that extend a base entity is an option tooLiaoning
This makes sense now, thanks. In fact I have several objects that need to be displayed in this type of widget so I will get some reuse out a Converter.Ozone
This problem eventually led me to Omnifaces SelectItemsConverter which looks like the way to go, for anyone facing the same issue.Ozone

© 2022 - 2024 — McMap. All rights reserved.