110

I want to make a selectOneMenu dropdown so I can select a status on my question. Is it possible to make the f:selectItem more flexible considering what happens if the order of the enums changes, and if the list was large? And could I do this better? And is it possible to automatically "select" the item that the question have?

Enum class

public enum Status {
    SUBMITTED,
    REJECTED,
    APPROVED
}

Question entity

@Enumerated(EnumType.STRING)
private Status status;

JSF

<div class="field">
    <h:outputLabel for="questionStatus" value="Status" />
    <h:selectOneMenu id="questionStatus" value="#{bean.question.status}" >
        <f:selectItem itemLabel="Submitted" itemValue="0" />
        <f:selectItem itemLabel="Rejected" itemValue="1" />
        <f:selectItem itemLabel="Approved" itemValue="2" />
    </h:selectOneMenu>
    <hr />
</div>
Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
LuckyLuke
  • 47,771
  • 85
  • 270
  • 434

4 Answers4

225

JSF has a builtin converter for enum, so this should do:

@Named
@ApplicationScoped
public class Data {

    public Status[] getStatuses() {
        return Status.values();
    }

}

with

<h:selectOneMenu value="#{bean.question.status}" >
    <f:selectItems value="#{data.statuses}" />
</h:selectOneMenu>

(note: since JSF 2.0 there's no need anymore to provide a SelectItem[] or List<SelectItem>, a T[] and List<T> are accepted as well and you can access the current item by var attribute)

If you happen to use JSF utility library OmniFaces, then you could use <o:importConstants> instead of a bean.

<o:importConstants type="com.example.Status" />

<h:selectOneMenu value="#{bean.question.status}" >
    <f:selectItems value="#{Status}" />
</h:selectOneMenu>

If you intend to control the labels as well, you could add them to the Status enum:

public enum Status {

    SUBMITTED("Submitted"),
    REJECTED("Rejected"),
    APPROVED("Approved");

    private String label;

    private Status(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

}

with

<f:selectItems value="#{data.statuses}" var="status"
    itemValue="#{status}" itemLabel="#{status.label}" />

Or, better, make the enum value a property key of a localized resource bundle (EL 3.0 required):

<f:selectItems value="#{data.statuses}" var="status"
    itemValue="#{status}" itemLabel="#{text['data.status.' += status]}" />

with this in a properties file associated with resource bundle #{text}

data.status.SUBMITTED = Submitted
data.status.REJECTED = Rejected
data.status.APPROVED = Approved
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • One thing BalusC, is it possible to "select"/view the status that a question has as default (for example when you are editing a question then you have already set the status of the question to something) – LuckyLuke Nov 22 '11 at 17:18
  • In the above example, JSF will do it by default when `#{bean.question.status}` has a valid enum value. You don't need to do anything expect of ensuring that the `question` has the proper status property prefilled. – BalusC Nov 22 '11 at 17:18
  • @BalusC How to access ordinal value from JSF? – jacktrades Feb 15 '13 at 18:13
  • 2
    If, like me, you get a number format exception for `+= status`, then try using `.concat(status)` as @Ziletka suggests. – whistling_marmot May 20 '16 at 08:20
  • If you prefer java.util.List you can just modify getStatuses() return type to List and return Arrays.asList(Status.values()); – stakahop Oct 04 '16 at 12:42
  • Due to inheritance (in my case) JSF isn't able to detect the target type as an enum. Is there a way to force JSF to convert the value? – thomas.mc.work Mar 01 '17 at 09:51
  • If I want to control the labels of the `selectOneMenu` 's items with something like: `` by `msg` is my message bundle and `i18nKey` is an all-uppercase key in my message bundle. – Roland Sep 19 '17 at 21:38
  • How does it happend that `` is calling `Status.values()`. Why is it sufficient and why do you prefer it over ``. Thanks :-) – Fabian Barney Jun 20 '18 at 09:29
  • @FabianBarney Click the link behind to learn about its working. – BalusC Jun 20 '18 at 13:45
  • I did not find the answer to my question on that page. Furthermore I did not find the answer why you prefer `` over ``. I was just interested in your personal preferences and why you choose the one way over the other. But no problem. – Fabian Barney Jun 20 '18 at 14:01
  • The Primefaces library also has the `` tag – Ken Schumacher Jul 20 '22 at 06:08
17

For localization we can use also this solution:

public enum Status { SUBMITTED, REJECTED, APPROVED }

data.status.SUBMITTED=Submitted
data.status.REJECTED=Rejected
data.status.APPROVED=Approved

<h:selectOneMenu value="#{bean.question.status}" >
    <f:selectItems
        value="#{data.statuses}"
        var="status"
        itemValue="#{status}"
        itemLabel="#{text['data.status.'.concat(status)]}" />
</h:selectOneMenu>

So the resource path for localization strings are not hardcoded in Enum.

sasynkamil
  • 859
  • 2
  • 12
  • 23
  • 1
    Note that this syntax is only supported since EL 2.2 which is "relatively" new. Otherwise you can always grab `` or `` or homebrew a custom EL function. – BalusC Apr 05 '12 at 21:49
  • Thank you BalusC. Is it possible to somehow replace #{data.statuses} with enum Class, without using backing bean (e.g. value="#{org.myproject.Status.values}")? – sasynkamil Apr 06 '12 at 17:08
  • @BalusC are you sure? I'm using GF 3.1.2 (Mojarra JSF 2.1.6). – sasynkamil Apr 06 '12 at 17:21
4

You could use <f:selectItems value="#{carBean.carList}" /> and return a list of SelectItem instances that wrap the enum (use Status.values() to get all possible values).

Thomas
  • 87,414
  • 12
  • 119
  • 157
2

You can use following utility el function to obtain the enum values and use them in a SelectOneMenu for example. No need to create beans and boilerplate methods.

public final class ElEnumUtils
{
    private ElEnumUtils() { }

    /**
     * Cached Enumerations, key equals full class name of an enum
     */
    private final static Map<String, Enum<?>[]> ENTITY_ENUMS = new HashMap<>();;

    /**
     * Retrieves all Enumerations of the given Enumeration defined by the
     * given class name.
     *
     * @param enumClassName Class name of the given Enum.
     *
     * @return
     *
     * @throws ClassNotFoundException
     */
    @SuppressWarnings("unchecked")
    public static Enum<?>[] getEnumValues(final String enumClassName) throws ClassNotFoundException
    {
        // check if already cached - use classname as key for performance reason
        if (ElEnumUtils.ENTITY_ENUMS.containsKey(enumClassName))
            return ElEnumUtils.ENTITY_ENUMS.get(enumClassName);

        final Class<Enum<?>> enumClass = (Class<Enum<?>>) Class.forName(enumClassName);

        final Enum<?>[] enumConstants = enumClass.getEnumConstants();

        // add to cache
        ElEnumUtils.ENTITY_ENUMS.put(enumClassName, enumConstants);

        return enumConstants;
    }
}

Register it as an el function in a taglib file:

<function>
    <description>Retrieves all Enumerations of the given Enumeration defined by the given class name.</description>
    <function-name>getEnumValues</function-name>
    <function-class>
        package.ElEnumUtils
    </function-class>
    <function-signature>
        java.lang.Enum[] getEnumValues(java.lang.String)
    </function-signature>
</function>

And finally call it like:

<p:selectOneMenu value="#{bean.type}">
    <f:selectItems value="#{el:getEnumValues('package.BeanType')}" var="varEnum" 
        itemLabel="#{el:getEnumLabel(varEnum)}" itemValue="#{varEnum}"/>
</p:selectOneMenu>

Similiar to BalusC answer you should be using a resource bundle with localized enum labels and for cleaner code you can also create a function like getEnumLabel(enum)

djmj
  • 5,579
  • 5
  • 54
  • 92
  • No need for a "function" (method more), when you can use `#{myBundle[enumName.i18nKey]}` and then put the i18n keys into your enumeration as properties: `BLA_TYPE("SOME_BLA_TYPE_KEY")` by `BLA_TYPE` is the enum to be used and `SOME_BLA_TYPE_KEY` is the i18n key. – Roland Sep 20 '17 at 18:32