Cómo usar valores de enumeración en f: selectItem (s)

103

Quiero hacer un menú desplegable selectOneMenu para poder seleccionar un estado en mi pregunta. ¿Es posible hacer que f: selectItem sea más flexible considerando lo que sucede si cambia el orden de las enumeraciones y si la lista es grande? ¿Y podría hacer esto mejor? ¿Y es posible "seleccionar" automáticamente el elemento que tiene la pregunta?

Clase de enumeración

public enum Status {
    SUBMITTED,
    REJECTED,
    APPROVED
}

Entidad de pregunta

@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>
LuckyLuke
fuente

Respuestas:

210

JSF tiene un convertidor incorporado para enum, así que esto debería funcionar:

@ManagedBean
@ApplicationScoped
public class Data {

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

}

con

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

(nota: dado que JSF 2.0 ya no es necesario proporcionar un SelectItem[]o List<SelectItem>, un T[]y también List<T>se aceptan y puede acceder al elemento actual por varatributo)

Si usa la biblioteca de utilidades JSF OmniFaces , entonces podría usar en <o:importConstants>lugar de un bean.

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

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

Si también tiene la intención de controlar las etiquetas, puede agregarlas a la Statusenumeración:

public enum Status {

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

    private String label;

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

    public String getLabel() {
        return label;
    }

}

con

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

O, mejor, convierta el valor de enumeración en una clave de propiedad de un paquete de recursos localizado (se requiere EL 3.0):

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

con esto en un archivo de propiedades asociado con el paquete de recursos #{text}

data.status.SUBMITTED = Submitted
data.status.REJECTED = Rejected
data.status.APPROVED = Approved
BalusC
fuente
Una cosa, BalusC, es que es posible "seleccionar" / ver el estado que tiene una pregunta como predeterminado (por ejemplo, cuando está editando una pregunta, entonces ya ha establecido el estado de la pregunta en algo)
LuckyLuke
En el ejemplo anterior, JSF lo hará de forma predeterminada cuando #{bean.question.status}tenga un valor de enumeración válido. No necesita hacer nada para asegurarse de que questiontiene la propiedad de estado adecuada precargada.
BalusC
@BalusC ¿Cómo acceder al valor ordinal desde JSF?
jacktrades
2
Si, como yo, obtiene una excepción de formato de número para += status, intente usarlo .concat(status)como sugiere @Ziletka.
whistling_marmot
Si prefiere java.util.List, simplemente puede modificar el tipo de retorno getStatuses () a List <Status> y devolver Arrays.asList (Status.values ​​());
stakahop
16

Para la localización también podemos utilizar esta solución:

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>

Entonces, la ruta de recursos para las cadenas de localización no está codificada en Enum.

Sasynkamil
fuente
1
Tenga en cuenta que esta sintaxis solo se admite desde EL 2.2, que es "relativamente" nueva. De lo contrario, siempre puede tomar <c:set>o <ui:param>crear una función EL personalizada.
BalusC
Gracias BalusC. ¿Es posible reemplazar de alguna manera # {data.statuses} con enum Class, sin usar un bean de respaldo (por ejemplo, value = "# {org.myproject.Status.values}")?
sasynkamil
@BalusC, ¿estás seguro? Estoy usando GF 3.1.2 (Mojarra JSF 2.1.6).
sasynkamil
4

Puede usar <f:selectItems value="#{carBean.carList}" />y devolver una lista de SelectIteminstancias que envuelvan la enumeración (use Status.values()para obtener todos los valores posibles).

Thomas
fuente
2

Puede usar la siguiente función de utilidad el para obtener los valores de enumeración y usarlos en, SelectOneMenupor ejemplo. No es necesario crear frijoles y métodos repetitivos.

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

Regístrelo como una función el en un archivo taglib:

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

Y finalmente llámalo como:

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

Similar a la respuesta de BalusC, debería usar un paquete de recursos con etiquetas de enumeración localizadas y para un código más limpio también puede crear una función como getEnumLabel(enum)

djmj
fuente
No se necesita una "función" (método más), cuando puede usar #{myBundle[enumName.i18nKey]}y luego poner las claves i18n en su enumeración como propiedades: BLA_TYPE("SOME_BLA_TYPE_KEY")by BLA_TYPEes la enumeración que se utilizará y SOME_BLA_TYPE_KEYes la clave i18n.
Roland