web-dev-qa-db-fra.com

JSF 2.0: utilisez les valeurs Enum pour selectOneMenu

J'utilise JSF 2.0 et je veux remplir un selectOneMenu avec les valeurs de mon Enum. Un exemple simple:

// Sample Enum
public enum Gender {
  MALE("Male"),
  FEMALE("Female");

  private final String label;

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

  public String getLabel() {
    return this.label;
  }
}

Malheureusement, je ne peux pas utiliser Seam pour mon projet actuel, qui avait une belle <s:convertEnum/> Tag qui a effectué la majeure partie du travail. Dans Seam, pour utiliser les valeurs de l'énumération, j'ai dû écrire le balisage suivant (et créer une fabrique qui fournit le #{genderValues}:

<!-- the Seam way -->
<h:selectOneMenu id="persongender" value="#{person.gender}">
  <s:selectItems var="_gender" value="#{genderValues}"" label="#{_gender.label}"/>
  <s:convertEnum/>
</h:selectOneMenu>

Le résultat est que je n'ai plus à déclarer explicitement les valeurs Enum à l'intérieur du balisage. Je sais que ce n'est pas très facile dans JSF <2.0, mais y a-t-il des nouveautés dans JSF2 pour résoudre ce problème? Ou Weld aide-t-il ici d'une manière ou d'une autre? S'il n'y a rien de nouveau dans JSF2, quelle est la façon la plus simple de le faire dans JSF 1.2?

Ou puis-je même intégrer la balise Seam JSF et les classes correspondantes de Seam pour obtenir la même fonctionnalité dans une application JavaEE6 (sans le conteneur Seam)?

27
Ingo Fischer

Ok, voici la dernière façon: - Enregistrez le convertisseur d'énumération standard dans faces-config.xml (facultatif):

<converter>
  <converter-for-class>Java.lang.Enum</converter-for-class>
  <converter-class>javax.faces.convert.EnumConverter</converter-class>
</converter>

Ajoutez par exemple une fonction à un bean géré qui convertit les valeurs Enum en un tableau de SelectItems:

@ManagedBean
public class GenderBean {
  public SelectItem[] getGenderValues() {
    SelectItem[] items = new SelectItem[Gender.values().length];
    int i = 0;
    for(Gender g: Gender.values()) {
      items[i++] = new SelectItem(g, g.getLabel());
    }
    return items;
  }
}

Liez ensuite cette fonction à selectOneMenu dans JSF:

<h:selectOneMenu id="gender" value="#{person.gender}">
  <!-- use property name not method name -->
  <f:selectItems value="#{genderBean.genderValues}" />
</h:selectOneMenu>

C'est ça! Pas la première explication de ce problème sur le net. Mais je pense que c'est le plus simple et le plus court;)

48
Ingo Fischer

Après avoir regardé mon propre exemple Seam pendant une minute, j'ai créé une méthode dans un bean géré comme ceci:

@ManagedBean
public class MyManagedBean {
  public Gender[] getGenderValues() {
    return Gender.values;
  }
}   

Et dans mon balisage je mets

<h:selectOneMenu id="gender" value="#{person.gender}">
  <f:selectItems value="#{myManagedBean.genderValues}" var="g" 
    itemValue="#{g}" itemLabel="#{g.label}"/>
</h:selectOneMenu>

Maintenant, je vais devoir voir si le enum est enregistré correctement dans mon entité lorsque le formulaire est envoyé. Je vais voir si je peux le faire moi-même - de toute façon, j'apprécierais des conseils ou des meilleures pratiques à ce sujet!

21
Ingo Fischer

Voici une méthode plus simple qui utilise un getter et un setter simples pour rassembler les chaînes en énumérations.

http://www.ninthavenue.com.au/blog/using-enums-in-el

5
Roger Keays

J'ai rencontré ce problème il y a quelque temps et je l'ai résolu comme vous l'avez fait, mais j'ai réalisé à un moment donné qu'avec cette solution, je ne pouvais pas utiliser i18n car les chaînes étaient codées en dur dans la classe enum. J'ai donc modifié mon enumConverter pour utiliser messages pour le rendu.

Parfois, vous souhaiterez peut-être rendre l'énumération sous la forme d'un identifiant unique et non sous forme de texte lisible par l'utilisateur (pour une utilisation interne dans certains composants).

Voici mon convertisseur:

import Java.util.ResourceBundle;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.Apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */




import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import com.eyeprevent.configuration.ConfigurationReader;


/**
 * converts an enum in a way that makes the conversion reversible (sometimes)
 * <ul>
 * <li>input: uses its classname and ordinal, reversible<li>
 * <li>else: uses its name, non reversible<li>
 * </ul>
 */
public class EnumConverter implements Converter
{
    @SuppressWarnings("unchecked")
    public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException
    {
        if (value == null || value.length() < 1)
        {
            return null;
        }

        int pos = value.indexOf('@');
        if (pos < 0)
        {
            throw new IllegalArgumentException(value + " do not point to an enum");
        }

        String className = value.substring(0, pos);
        Class clazz;
        int ordinal = Integer.parseInt(value.substring(pos+1), 10);

        try
        {
            clazz = Class.forName( className, true, Thread.currentThread().getContextClassLoader() );
            // if the clazz is not an enum it might be an enum which is inherited. In this case try to find the superclass.
            while (clazz != null && !clazz.isEnum())
            {
                clazz = clazz.getSuperclass();
            }
            if (clazz == null)
            {
                throw new IllegalArgumentException("class " + className + " couldn't be treated as enum");
            }

            Enum[] enums = (Enum[]) clazz.getEnumConstants();
            if (enums.length >= ordinal)
            {
                return enums[ordinal];
            }
        }
        catch (ClassNotFoundException e1)
        {
            throw new RuntimeException(e1);
        }

        throw new IllegalArgumentException("ordinal " + ordinal + " not found in enum " + clazz);
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException
    {
        if (value == null)
        {
            return "";
        }

        Enum<?> e = (Enum<?>) value;

        if (component instanceof UIInput || UIInput.COMPONENT_FAMILY.equals(component.getFamily()))
        {
            return e.getClass().getName() + "@" + Integer.toString(e.ordinal(), 10);
        }
        ResourceBundle messages =ConfigurationReader.getMessages(context.getViewRoot().getLocale());
        return messages.getString(e.name());

    }
}
4
pakore

J'utilise cette approche simple, elle est assez optimiste, vous pouvez la personnaliser à votre guise. J'ai mis le code suivant dans un bean réutilisable, qui peut être appelé à tout moment depuis votre application, afin que vous puissiez utiliser n'importe laquelle de vos énumérations déclarées dans votre package.

public List<String> fromEnum(String cname) {
        List<String> names = new ArrayList<>();
        try {
            Class c = Class.forName(cname);
            Object[] r = c.getEnumConstants();
            if (r != null) {
                for (Object o : r) {
                    names.add(o.toString());
                }
            }
        } catch (ClassNotFoundException ex) {
            FaceUtil.ShowError(ex);
        }
        return names;
    }
public static void ShowError(Exception ex) {
        FacesMessage msg=new FacesMessage(FacesMessage.SEVERITY_ERROR,ex.getMessage(),"Error Message");
        FacesContext.getCurrentInstance().addMessage(null, msg);
        }

Maintenant, utilisez-le dans le fichier xhtml comme suit:

<p:selectOneMenu value="#{jobapp.aplicant.marital}">
<f:selectItems value="#{rtutil.fromEnum('com.company.package.enMarital')}" var="m" itemLabel="#{m}" itemValue="#{m}"/>
</p:selectOneMenu>
2
MTom