How to use enum values in f:selectItem(s)

JsfJsf 2EnumsSelectonemenu

Jsf Problem Overview


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>

Jsf Solutions


Solution 1 - Jsf

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

Solution 2 - Jsf

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.

Solution 3 - Jsf

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

Solution 4 - Jsf

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLuckyLukeView Question on Stackoverflow
Solution 1 - JsfBalusCView Answer on Stackoverflow
Solution 2 - JsfsasynkamilView Answer on Stackoverflow
Solution 3 - JsfThomasView Answer on Stackoverflow
Solution 4 - JsfdjmjView Answer on Stackoverflow