JSF 2: Using enums in the rendered attribute
Asked Answered
T

4

19

Is there any way to check declaratively whether an enum has a specified value. For example:

<h:graphicImage name="error.png" library="images" 
  rendered="#{viewController.current.status == Status.ERROR}" />

It's a little bit tedious to define a method in the managed beand that checks this for every enum value, e.g.

public boolean isStateIsError() {
  return current.getStatus() == Status.ERROR;
}

Is there a shorter/better way of doing this?

Technicality answered 16/1, 2011 at 17:2 Comment(0)
T
39

Until EL 3.0 it's not possible to import enums in EL scope. You can however just treat and compare them like strings, i.e. the enum constant value must be quoted like below.

<h:graphicImage name="error.png" library="images" 
  rendered="#{viewController.current.status eq 'ERROR'}" />
Thibeault answered 16/1, 2011 at 17:4 Comment(1)
String comparison is evaluated like calling equals() method or like comparison by reference?Grovel
B
9

I know this question is a bit older now, but i had the same problem and found another solution, which i want to share :

Create a Custom EL-Resolver and use enums and java constants as objects in jsf el:

<h:graphicImage name="error.png" library="images"  
      rendered="#{viewController.current.status == Status.ERROR}" />

But before you can use enums this way you have to do 3 steps.

1. step - Copy this Class and replace "MY_ENUM" through your enumClass (in the example above it would be "Status")

public class EnumCache {
    private Map<String, Object>  propertCache = new HashMap<String, Object>();
    private Map<String, Class>  baseCache = new HashMap<String, Class>();
    private static EnumCache staticEnumCache = null;

    public static EnumCache instance() {
        if (staticEnumCache == null) { staticEnumCache = new EnumCache(); }
        return staticEnumCache;
    }
    private EnumCache() {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        classes.add(MY_ENUM.class);

        for(Class clazz : classes) {
            try {
                baseCache.put(clazz.getSimpleName(), clazz);
                Method m = clazz.getMethod("values", (Class[]) null);
                Enum<?>[] valueList = (Enum[]) m.invoke(null, (Object[]) null);
                for (Enum<?> en : valueList) {
                    propertCache.put(clazz.getSimpleName() + "." + en.name(), en);
                }
            } catch (Exception e) {
                System.err.println(clazz.getSimpleName(), e);
            }
        }
    }
    public Object getValueForKey(String key)  {
        return propertCache.get(key);
    }
    public Class getClassForKey(String key) {
        return baseCache.get(key);
    }
}

2. step - add this EnumResolver - This class will map your JSF expression to the enum in cache (step 1)

public class MyEnumResolver extends ELResolver {

    public Object getValue(ELContext context, Object base, Object property) {
        Object result = null;
        if (base == null) {
            result = EnumCache.instance().getClassForKey(property + "");
        } else if (base instanceof Class) {
            result = EnumCache.instance().getValueForKey(((Class) base).getSimpleName() + "." + property);
        }
        if (result != null) {
            context.setPropertyResolved(true);
        }
        return result;
    }

    public Class<?> getCommonPropertyType(ELContext context, Object base) {
        return null;
    }
    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
        return null;
    }
    public Class<?> getType(ELContext context, Object base, Object property) {
        return null;
    }
    public boolean isReadOnly(ELContext context, Object base, Object property) {
        return false;
    }
    public void setValue(ELContext context, Object base, Object property, Object arg3) {
    }
}

3. step - register the EnumResolver in faces-config.xml

<faces-config>
    <application>
        <el-resolver>com.asd.MyEnumResolver</el-resolver>
    </application>
</faces-config>

NOTE: If you want to access your java constants this way, you just have to extend the constructor of the enumCache class. This (untestet) example should work:

baseCache.put(CLASS_WITH_CONSTANTS.getSimpleName(), clazz);
for (Field field : CLASS_WITH_CONSTANTS.getDeclaredFields()) {
    try {
        propertCache.put(CLASS_WITH_CONSTANTS.getSimpleName() + "." 
                  + field.getName(), field.get(null));
    } catch (Exception e) { }
}

Hope this reduced but working code can help anybody.


Update

I see this benefits:

  1. If you use strings in jsf (viewController.current.status == 'ERROR_abcdefg'), you can misspell the value and wont recognise it so fast. With my solution you would get an error while loading the jsf file, because the enum could not be resolved.

  2. You can see in the sourcecode that "ERROR" is value of the enum "STATUS".

  3. When you compare two values in el, the class of the enums will be compared too. So for example PersonState.ACTIV is not the same like AccounState.ACTIV.

  4. When i have to change my enum value from PersonState.ACTIV to PersonState.ACTIVATED i can search for the String "PersonState.ACTIV" in my sourcecode. searching for "ACTIV" would have much more matches.

Bibliofilm answered 27/9, 2012 at 8:8 Comment(2)
What benefits exactly does this approach give you? In Java, the main advantage of using direct enum references was compile time enforcement of valid values, but this advantage doesn't exist in EL, also not with this resolver.Thibeault
This is even more tedious than creating a method in your managed bean! Still, it's nice to know this can be done.Skiascope
M
1

I solved a similar problem by statically dumping all the enum keys (which are used in the rendered UI components) in a map and then I use a static getByKey method to convert the value from the UI into an actual native enum in the setter, throwing an Exception if the value provided is invalid:

public enum ReportType {

    FILING("F", "Filings"),
    RESOLUTION("R", "Resolutions"),
    BASIS("B", "Bases"),
    STAFF("T", "Staff Counts"),
    COUNTS("I", "Counts");

    private String key;
    private String label;

    private static Map<String, ReportType> keyMap = new HashMap<String, ReportType>();  

    static {
        for(ReportType type : ReportType.values()) {
            keyMap.put(type.getKey(), type);
        }
    }

    private ReportType(String _key, String _label) {
        this.key = _key;
        this.label = _label;

    }

    public String getKey() {
        return this.key;
    }

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

    public static List<ReportType> getValueList() {
        return Arrays.asList(ReportType.values());
    }

    public static ReportType getByKey(String _key) {
        ReportType result = keyMap.get(_key);

        if(result == null) {
            throw new IllegalArgumentException("Invalid report type key: " + _key);
        }

        return result;
    }
}

In the UI tier, the enum key is used as the value and the enum label is used as the label:

<f:selectItems var="rptTypeItem" value="#{reportController.allReportTypes}" 
    itemLabel="#{rptTypeItem.label}" itemValue="#{rptTypeItem.key}"/>

In the managed bean, I convert the enum into a renderable list, using the getValueList() from the enum:

public List<ReportType> getAllReportTypes() {
    return ReportType.getValueList();
}

Finally, the [g|s]etters in the managed bean look as follows:

public String getReportType() {
    return this.crtRptType.getKey();
}

public void setReportType(String _val) {
    this.crtRptType = ReportType.getByKey(_val);
}
Morphosis answered 27/3, 2015 at 22:17 Comment(0)
R
1

I think it could be done it the following way:

Create a method in you bean that would return the list of enums, for example

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

then you can use the enum in EL like this

<h:graphicImage name="error.png" library="images" 
  rendered="#{viewController.current.status == someBean.statuses[0]}" />

assuming that the order of enum members is not going to be changed (for ex. here statuses[0] is ERROR). However, I would fix the positions like this:

public Status[] getStatuses() {
  Status myStatuses = new Status [2]; // or whatever number of statuses you are going to use in UI
  myStatuses [0] = Status.ERROR;
  myStatuses [1] = Status.RUNNING;
  return myStatuses;
}

This is still not dynamic solution, but it's better than hard-coding in EL. Might be especially useful when you'r using localization for you statuses (enum values depending on locale/translation).

Rugby answered 10/3, 2016 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.