Como usar valores enum em f: selectItem (s)

103

Desejo criar uma lista suspensa selectOneMenu para poder selecionar um status para minha pergunta. É possível tornar o f: selectItem mais flexível, considerando o que acontece se a ordem dos enums muda e se a lista é grande? E eu poderia fazer isso melhor? E é possível "selecionar" automaticamente o item que a pergunta possui?

Classe Enum

public enum Status {
    SUBMITTED,
    REJECTED,
    APPROVED
}

Entidade de pergunta

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

Respostas:

210

JSF tem um conversor embutido para enum, então isso deve fazer:

@ManagedBean
@ApplicationScoped
public class Data {

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

}

com

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

(observação: desde o JSF 2.0 não há mais necessidade de fornecer um SelectItem[]ou List<SelectItem>, um T[]e também List<T>são aceitos e você pode acessar o item atual por varatributo)

Se acontecer de você usar a biblioteca de utilitários JSF OmniFaces , poderá usar em <o:importConstants>vez de um bean.

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

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

Se você pretende controlar os rótulos também, pode adicioná-los ao Statusenum:

public enum Status {

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

    private String label;

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

    public String getLabel() {
        return label;
    }

}

com

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

Ou, melhor, torne o valor enum uma chave de propriedade de um pacote de recursos localizado (EL 3.0 obrigatório):

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

com isso em um arquivo de propriedades associado ao pacote de recursos #{text}

data.status.SUBMITTED = Submitted
data.status.REJECTED = Rejected
data.status.APPROVED = Approved
BalusC
fonte
Uma coisa BalusC, é possível "selecionar" / ver o status que uma questão tem como padrão (por exemplo, quando você está editando uma questão, então você já definiu o status da questão para algo)
LuckyLuke
No exemplo acima, o JSF fará isso por padrão quando #{bean.question.status}tiver um valor enum válido. Você não precisa fazer nada além de garantir que o questiontenha a propriedade de status adequada pré-preenchida.
BalusC de
@BalusC Como acessar o valor ordinal do JSF?
jacktrades de
2
Se, como eu, você obtiver uma exceção de formato de número para += status, tente usar .concat(status)como @Ziletka sugere.
whistling_marmot
Se você preferir java.util.List, pode apenas modificar o tipo de retorno getStatuses () para List <Status> e retornar Arrays.asList (Status.values ​​());
stakahop
16

Para localização, podemos usar também esta solução:

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>

Portanto, o caminho do recurso para strings de localização não é codificado no Enum.

Ziletka
fonte
1
Observe que essa sintaxe só é suportada desde EL 2.2, que é "relativamente" novo. Caso contrário, você sempre pode obter <c:set>ou fazer <ui:param>um homebrew de uma função EL personalizada.
BalusC
Obrigado BalusC. É possível substituir de alguma forma # {data.statuses} por enum Class, sem usar o bean de apoio (por exemplo, value = "# {org.myproject.Status.values}")?
Ziletka
@BalusC tem certeza? Estou usando o GF 3.1.2 (Mojarra JSF 2.1.6).
Ziletka
4

Você pode usar <f:selectItems value="#{carBean.carList}" />e retornar uma lista de SelectIteminstâncias que envolvem o enum (use Status.values()para obter todos os valores possíveis).

Thomas
fonte
2

Você pode usar o seguinte utilitário el função para obter os valores enum e usá-los em um, SelectOneMenupor exemplo. Não há necessidade de criar beans e métodos clichê.

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

Registre-o como uma função el em um arquivo 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>

E, finalmente, chame-o assim:

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

Semelhante à resposta BalusC, você deve usar um pacote de recursos com rótulos enum localizados e para um código mais limpo, você também pode criar uma função como getEnumLabel(enum)

djmj
fonte
Não há necessidade de uma "função" (mais método), quando você pode usar #{myBundle[enumName.i18nKey]}e, em seguida, colocar as chaves i18n em sua enumeração como propriedades: BLA_TYPE("SOME_BLA_TYPE_KEY")by BLA_TYPEé o enum a ser usado e SOME_BLA_TYPE_KEYé a chave i18n.
Roland de