2012-05-22 10 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 –

相關問題