Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort a list that will be localized in JSF output

In my application I have a list of keys (strings), where the user can select one of them. In the user-interface, the keys will be output according to the current locale:

<h:selectOneMenu value="#{bean.selectedKey}">
  <f:selectItems value="#{bean.allKeys}" var="_key" itemLabel="#{msgs[_key]}" />
</h:selectOneMenu>

My setup uses a standard resource-bundle configured in faces-config.xml as explained in this answer by BalusC. msgs in the example above is the name of the resource-bundle variable.

What I want now, is the items from the selectOneMenu to be sorted in alphabetic order. Of course the order depends on the used locale. The problem is, I can't/won't do the sorting in the backing-bean, as I don't know how the JSF-page will output the keys.

This problem seems quite generic to me, so I'm wondering what the best practice is to solve this kind of problem.

(Of course the problem is not only applicable to selectOneMenu. Any list/collection that will be output in the user-interface suffers from the same problem.)

like image 540
Martin Höller Avatar asked Dec 25 '22 01:12

Martin Höller


1 Answers

You've basically 2 options.

  1. Sort in client side with a little help of JS. I'll for simplicity assume that you're using jQuery.

    <h:selectOneMenu ... styleClass="ordered">
    
    $("select.ordered").each(function(index, select) {
        var $select = $(select);
        $select.find("option").sort(function(left, right) {
            return left.text == right.text ? 0 : left.text < right.text ? -1 : 1;
        }).appendTo($select);
        if (!$select.find("option[selected]").length) {
            select.options.selectedIndex = 0;
        }
    });
    

  2. Sort in server side wherein you create List<SelectItem> and grab #{msgs} via injection. Assuming that you're using CDI and thus can't use @ManagedProperty("#{msgs}"), you'd need to create a producer for that first. I'll for simplicity assume that you're using OmniFaces (which is also confirmed based on your question history).

    public class BundleProducer {
    
        @Produces
        public PropertyResourceBundle getBundle() {
            return Faces.evaluateExpressionGet("#{msgs}");
        }
    
    }
    

    Then you can make use of it as follows in the backing bean associated with <f:selectItems value>:

    private static final Comparator<SelectItem> SORT_SELECTITEM_BY_LABEL = new Comparator<SelectItem>() {
        @Override
        public int compare(SelectItem left, SelectItem right) {
            return left.getLabel().compareTo(right.getLabel());
        }
    };
    
    private List<SelectItem> allKeys;
    
    @Inject
    private PropertyResourceBundle msgs;
    
    @PostConstruct
    public void init() {
        allKeys = new ArrayList<>();
    
        for (String key : keys) {
           allKeys.add(new SelectItem(key, msgs.getString(key)));
        }
    
        Collections.sort(allKeys, SORT_SELECTITEM_BY_LABEL);
    }
    

    And reference it directly without var as follows:

    <f:selectItems value="#{bean.allKeys}" />
    
like image 168
BalusC Avatar answered Dec 28 '22 05:12

BalusC