2012-05-22 11 views
4

我有以下问题:如何根据当前用户区域设置对Wicket下拉列表中的选项进行排序?

  • 一个下拉的元素列表
  • 这些元素具有固定密钥,该密钥使用的IChoiceRenderer执行查找关键的本地化版本(这是一个标准的实用程序渲染器,在不同的包中实现)
  • 本地化键的列表位于属性文件中,链接到实例化下拉列表的面板。

是否有一个优雅/可重复使用的解决方案,让下拉菜单显示其元素按字母顺序排序?

回答

1

使用这个扩展DropDownChoice的使用Java的Collator(基本语言环境敏感的排序 - 以民族特色和国家的排序规则考虑)

代码检票6和Java测试5+:

import java.text.Collator; 
import java.util.Comparator; 
import java.util.List; 
import java.util.Locale; 

import org.apache.wicket.Session; 
import org.apache.wicket.markup.html.form.DropDownChoice; 
import org.apache.wicket.markup.html.form.IChoiceRenderer; 
import org.apache.wicket.model.IModel; 

import com.google.common.collect.Ordering; 

/** 
* DropDownChoice which sort its choices (or in HTML's terminology select's options) according it's localized value 
* and using current locale based Collator so it's sorted how it should be in particular language (ie. including national characters, 
* using right order). 
* 
* @author Michal Bernhard [email protected] 2013 
* 
* @param <T> 
*/ 
public class OrderedDropDownChoice<T> extends DropDownChoice<T> { 

    public OrderedDropDownChoice(String id, IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T> renderer) { 
     super(id, choices, renderer); 
    } 

    public OrderedDropDownChoice(String id, IModel<? extends List<? extends T>> choices) { 
     super(id, choices); 
    } 

    public OrderedDropDownChoice(String id) { 
     super(id); 
    } 

    public OrderedDropDownChoice(
      String id, 
      IModel<T> model, 
      IModel<? extends List<? extends T>> choices, 
        IChoiceRenderer<? super T> renderer) { 

     super(id, model, choices, renderer); 
    } 

    @Override 
    public List<? extends T> getChoices() { 
     List<? extends T> unsortedChoices = super.getChoices(); 
     List<? extends T> sortedChoices = Ordering.from(displayValueAlphabeticComparator()).sortedCopy(unsortedChoices); 

     return sortedChoices; 
    } 

    private Collator localeBasedTertiaryCollator() { 
     Locale currentLocale = Session.get().getLocale(); 
     Collator collator = Collator.getInstance(currentLocale); 
     collator.setStrength(Collator.TERTIARY); 

     return collator; 
    } 

    private Comparator<T> displayValueAlphabeticComparator() { 

     final IChoiceRenderer<? super T> renderer = getChoiceRenderer(); 

     return new Comparator<T>() { 

      @Override 
      public int compare(T o1, T o2) { 
       Object o1DisplayValue = renderer.getDisplayValue(o1); 
       Object o2DisplayValue = renderer.getDisplayValue(o2); 

       return localeBasedTertiaryCollator().compare(o1DisplayValue, o2DisplayValue); 
      } 
     }; 

    } 


} 

https://gist.github.com/michalbcz/7236242

复制
+0

更加优雅 –

+0

我将此作为可接受的解决方案,用于记录,因此也许您可以将代码完全放入解决方案中。 –

+0

@FrédéricDonckels完成了,感谢您的反馈 –

0

我们公司使用的解决方案是基于Javascript的,我们在我们想要排序的下拉列表中设置了一个特殊的css类,并且一个小小的jQuery技巧也是这样。

+0

作为最后的手段,这正是我想要做的。 –

1

如果你想要一个基于Wicket的解决方案,您可以尝试到列表中与类似的东西进行排序:

public class ChoiceRendererComparator<T> implements Comparator<T> { 

    private final IChoiceRenderer<T> renderer; 

    public ChoiceRendererComparator(IChoiceRenderer<T> renderer) { 
     this.renderer = renderer; 
    } 

    @SuppressWarnings("unchecked") 
    public int compare(T o1, T o2) { 
     return ((Comparable<Object>) renderer.getDisplayValue(o1)).compareTo(renderer.getDisplayValue(o2)); 
    } 
} 

用法:

List<Entity> list = ... 
    IChoiceRenderer<Entity> renderer = ... 
    Collections.sort(list, new ChoiceRendererComparator<Entity>(renderer)); 
    DropDownChoice<Entity> dropdown = new DropDownChoice<Entity>("dropdown", list, renderer); 
+0

这就是我想要做的,但为了正确使用渲染器,您必须完全完成层次结构(否则,wicket的本地化组件无法正确搜索翻译并发出警告消息)。 –

+0

@FDO你应该覆写onInitialize()而不是在构造函数中工作。自Wicket 1.5以来,建议您这样做。然后等级被初始化。 –

+0

正确。我需要再试一次。 –

0

面临同样的问题,我感动的部分从我的XML到数据库的本地化数据,实现了匹配的Resolver,并能够使用本地化的Strings进行排序。 表设计和休眠配置有点棘手,这里描述:Hibernate @ElementCollection - Better solution needed

资源加载是沿着这些线路:

public class DataBaseStringResourceLoader extends ComponentStringResourceLoader { 

    private static final transient Logger logger = Logger 
      .getLogger(DataBaseStringResourceLoader.class); 

    @Inject 
    private ISomeDAO someDao; 
    @Inject 
    private IOtherDao otherDao; 
    @Inject 
    private IThisDAO thisDao; 
    @Inject 
    private IThatDAO thatDao; 

    @Override 
    public String loadStringResource(Class<?> clazz, String key, Locale locale, 
      String style, String variation) { 
     String resource = loadFromDB(key, new Locale(locale.getLanguage())); 
     if (resource == null) { 
      resource = super.loadStringResource(clazz, key, locale, style, variation); 
     } 
     return resource; 
    } 

    private String loadFromDB(String key, Locale locale) { 
     String resource = null; 
     if (locale.getLanguage() != Locale.GERMAN.getLanguage() 
       && locale.getLanguage() != Locale.ENGLISH.getLanguage()) { 
      locale = Locale.ENGLISH; 
     } 
     if (key.startsWith("some") || key.startsWith("other") 
       || key.startsWith("this") || key.startsWith("that")) { 
      Integer id = Integer.valueOf(key.substring(key.indexOf(".") + 1)); 
      ILocalizedObject master; 
      if (key.startsWith("some")) { 
       master = someDao.findById(id); 
      } else if (key.startsWith("other")) { 
       master = otherDao.findById(id); 
      } else if (key.startsWith("this")){ 
       master = thisDao.findById(id); 
      } else { 
       master = thatDao.findById(id); 
      } 
      if (master != null && master.getNames().get(locale) != null) { 
       resource = master.getNames().get(locale).getName(); 
      } else if (master == null) { 
       logger.debug("For key " + key + " there is no master."); 
      } 
     } 
     return resource; 
    } 
[...] 
    } 
2

最后,我想用渲染可能是最好的办法。为了使其可重用和高效,我将其分离为行为。

下面的代码:

import org.apache.wicket.Component; 
import org.apache.wicket.behavior.Behavior; 
import org.apache.wicket.markup.html.form.AbstractChoice; 
import org.apache.wicket.markup.html.form.IChoiceRenderer; 

import java.util.ArrayList; 
import java.util.Comparator; 
import java.util.List; 

import static java.util.Arrays.sort; 

/** 
* This {@link Behavior} can only be used on {@link AbstractChoice} subclasses. It will sort the choices 
* according to their "natural display order" (i.e. the natural order of the display values of the choices). 
* This assumes that the display value implements {@link Comparable}. If this is not the case, you should 
* provide a comparator for the display value. An instance of this class <em>cannot be shared</em> between components. 
* Because the rendering can be costly, the sort-computation is done only once, by default, 
* unless you set to <code>false</code> the <code>sortOnlyOnce</code> argument in the constructor. 
* 
* @author donckels (created on 2012-06-07) 
*/ 
@SuppressWarnings({"unchecked"}) 
public class OrderedChoiceBehavior extends Behavior { 

    // ----- instance fields ----- 

    private Comparator displayValueComparator; 
    private boolean sortOnlyOnce = true; 
    private boolean sorted; 

    // ----- constructors ----- 

    public OrderedChoiceBehavior() { 
    } 

    public OrderedChoiceBehavior(boolean sortOnlyOnce) { 
     this.sortOnlyOnce = sortOnlyOnce; 
    } 

    public OrderedChoiceBehavior(boolean sortOnlyOnce, Comparator displayValueComparator) { 
     this.sortOnlyOnce = sortOnlyOnce; 
     this.displayValueComparator = displayValueComparator; 
    } 

    // ----- public methods ----- 

    @Override 
    public void beforeRender(Component component) { 
     if (this.sorted && this.sortOnlyOnce) { return;} 
     AbstractChoice owner = (AbstractChoice) component; 
     IChoiceRenderer choiceRenderer = owner.getChoiceRenderer(); 
     List choices = owner.getChoices(); 

     // Temporary data structure: store the actual rendered value with its initial index 
     Object[][] displayValuesWithIndex = new Object[choices.size()][2]; 
     for (int i = 0, valuesSize = choices.size(); i < valuesSize; i++) { 
      Object value = choices.get(i); 
      displayValuesWithIndex[i][0] = choiceRenderer.getDisplayValue(value); 
      displayValuesWithIndex[i][1] = i; 
     } 

     sort(displayValuesWithIndex, new DisplayValueWithIndexComparator()); 
     List valuesCopy = new ArrayList(choices); 
     for (int i = 0, length = displayValuesWithIndex.length; i < length; i++) { 
      Object[] displayValueWithIndex = displayValuesWithIndex[i]; 
      int originalIndex = (Integer) displayValueWithIndex[1]; 
      choices.set(i, valuesCopy.get(originalIndex)); 
     } 
     this.sorted = true; 
    } 

    public Comparator getDisplayValueComparator() { 
     return this.displayValueComparator; 
    } 

    // ----- inner classes ----- 

    private class DisplayValueWithIndexComparator implements Comparator<Object[]> { 

     // ----- Comparator ----- 

     public int compare(Object[] left, Object[] right) { 
      Object leftDisplayValue = left[0]; 
      Object rightDisplayValue = right[0]; 
      if (null == leftDisplayValue) { return -1;} 
      if (null == rightDisplayValue) { return 1;} 

      if (null == getDisplayValueComparator()) { 
       return ((Comparable) leftDisplayValue).compareTo(rightDisplayValue); 
      } else { 
       return getDisplayValueComparator().compare(leftDisplayValue, rightDisplayValue); 
      } 
     } 
    } 
} 
+0

感谢发布此结果真的帮了我很多,欢呼声 –

+1

这个解决方案的问题是,在某些情况下,它将错误的选择推送到模型。如果使用ChoiceRenderer,以便从特定选项的索引(请参阅ChoiceRenderer#getIdValue)呈现选项的id属性,并且您使用某种Ajax表单提交行为,那么它将不会调用beforeRender,因此getChoices是旧的未排序的...我创建了这个解决方案:https://gist.github.com/michalbcz/7236242 –

相关问题