views:

1590

answers:

3

I'm using JSF 2.0 and want to fill a selectOneMenu with the values of my Enum. A simple example:

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

Unfortunately, i can't use Seam for my current project, which had a nice <s:convertEnum/> Tag that did most of the work. In Seam, to use the values of the Enum, i had to write the following markup (and create a factory that provides the #{genderValues}:

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

The result is that i don't have to declare the Enum values explicitely anymore inside the markup. I know that this is not very easy in JSF <2.0, but is there any new in JSF2 to help with this issue? Or does Weld help here somehow? If there is nothing new in JSF2, what's the easiest way to do it in JSF 1.2?

Or can i even integrate the Seam JSF tag and the corresponding classes of Seam to get the same feature in a JavaEE6-App (without the Seam container)?

+3  A: 

Okay sorry it would have been better to try before i ask ;) After looking at my own Seam example for a minute i created a method in a managed bean like this:

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

And in my markup i put

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

Now i'll have to see if the enum is saved correctly in my entity when the form is sent. I'll see if i can do this myself - anyway, i would appreciate tips or best practices on this!

ifischer
Drat, I was busy typing an answer in exactly this line... But you're entirely right, that's the way how it's to be done! JSF 1.2 and 2.0 have a builtin generic enum converter as you found out :) For JSF 1.0 and 1.1 you would need to fill a `List<SelectItem>` in bean constructor yourself and write a converter specific for the enum. Also see [this answer](http://stackoverflow.com/questions/2548879/jsf-with-enum-validation-error-value-is-not-valid/2549041#2549041) for an example.
BalusC
+2  A: 

Ok, here is the final way: - Register the standard enum converter in faces-config.xml:

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

Add a function for example to a managed bean which converts the Enum values to an array of SelectItems:

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

Then bind this function to the selectOneMenu in JSF:

<h:selectOneMenu id="gender" value="#{person.gender}">
  <f:selectItems value="#{genderBean.getGenderValues}" />
</h:selectOneMenu>

That's it! Not the first explanation for this problem on the net. But i think it's the easiest & shortest one ;)

ifischer
You don't need to register the standard enum converter. Have you checked the link I posted in a comment on your another answer?
BalusC
+2  A: 

I run into this issue some time ago and I solved it as you did, but then I realized at some point that I with that solution I could not use i18n because the strings were hardcoded in the enum class. So I modified my enumConverter to use messages for rendering.

Also sometimes you may want to render the enum as some unique identifier and not as user readable text (for internal usage inside some components).

This is my converter:

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());

    }
}
pakore