selectOneMenu со сложными объектами, нужен ли конвертер?

Необходим ли преобразователь для <h:selectOneMenu> или <p:selectOneMenu> с произвольными классами, созданными пользователями, в качестве значений? Я имею в виду, должен ли следующий код работать без преобразователя?

<p:selectOneMenu value="#{bean.language}">
    <f:selectItems value="#{bean.allLanguages}" />
</p:selectOneMenu>

и

@Named(value = "bean")
@ConversationScoped
public class Bean {

    private Language language; // appropriate getter and setter are present

    public List<SelectItem> getAllLanguages() {
        // populates a list of select items with Strings as item labels and Languages as item values
    }

}

У меня есть аналогичный код с enum в качестве типа (язык), и он отлично работает. Но когда я заменяю тип обычным классом Java, я получаю ошибку преобразования.


person Arash Shahkar    schedule 15.02.2013    source источник


Ответы (2)


Здесь вам нужен преобразователь, так как JSF по умолчанию будет принимать строки, так вы его закодировали. JSF понятия не имеет, как преобразовать ваши псевдосущности в строки и наоборот.

Некоторые примечания:

1 . Ваш метод getAsString определяет ваш идентификатор для ваших сущностей/POJO, а не то, что JSF (или что-то еще) выбирает в качестве itemLabel.

2 . Ваш конвертер может копаться в БД для реальных сущностей, используя эту печально известную статью:

http://balusc.blogspot.de/2011/09/communication-in-jsf-20.html#ConvertingAndValidatingGETRequestParameters

Вы также можете использовать аннотации CDI с этим «шаблоном».

3 . Ваш value = "bean" является избыточным, и область выбора CDI обычно @ViewScoped. Однако вы должны иметь в виду, что CDI @Named + JSF @ViewScoped не работают вместе без использования Seam 3 или Apache MyFaces CODI.

person Kawu    schedule 15.02.2013
comment
Спасибо, Каву. Мне просто интересно, почему точно такой же код работает, если язык имеет тип enum. - person Arash Shahkar; 15.02.2013
comment
Я предполагаю, что перечисления можно преобразовать в строку без дальнейшего преобразования, потому что все перечисления имеют общую реализацию (docs.oracle.com/javase/7/docs/api/java/lang/Enum.html), состоящий из метода .name(). Из-за этого в JSF также можно напрямую сравнивать конкретные перечисления, например. {employeeManager.mode eq 'EDIT'}, где перечисления Mode называются ADD, EDIT, REMOVE. - person Kawu; 15.02.2013
comment
О, и нет необходимости, чтобы метод bean возвращал List<SelectItem>. Используйте преобразователь по рецепту BalusC. - person Kawu; 15.02.2013
comment
Пожалуйста, скажите мне, если я ошибаюсь в этом. Идея преобразователя, безусловно, работает, чтобы опустить список SelectItems, только если itemLabels является свойством фактических значений, хранящихся в возвращаемом списке. Однако в моем приложении это не так. Метки генерируются на основе значения и не сохраняются как одно из его свойств. - person Arash Shahkar; 15.02.2013
comment
Только одно. Я заметил, что при наличии обоих методов, возвращающих List<SelectItem> и FacesConverter, некоторая часть логики преобразователя дублируется, что указывает на плохую идею. Учитывая требование, которое я упомянул в последнем комментарии, кажется, что метод для создания метки элемента из его значения, вызываемый <f:selectItems ... itemLabel="#{bean.method(item)}" />, - это путь. - person Arash Shahkar; 15.02.2013

Вам не нужен преобразователь, если вы используете этот небольшой класс, который я написал :-) Он может возвращать компоненты selectOne и selecteMany. Это требует, чтобы toString() вашего класса обеспечивала однозначное уникальное представление вашего объекта. Если хотите, вы можете заменить имя метода, отличное от toString(), например toIDString().

Чтобы использовать ListBacker в ManagedBean, используйте ListBacker<Type> везде, где вы использовали бы List<Type>

@ManagedBean
@RequestScoped
public class BackingBean {
    private ListBacker<User> users; // +getter +setter

    @PostConstruct
    public void init() {
        // fill it up from your DAO
        users = new ListBacker<User>(userDAO.find());
    }
    // Here's the payoff!  When you want to use the selected object, 
    // it is just available to you, with no extra database hits:
    User thisOneIsSelected = users.getSelectedItemAsObject();

    // or for multi-select components:
    List<User> theseAreSelected = users.getSelectedItemsAsObjects();
}

В вашем xhtml-файле:

<p:selectOneMenu value="#{backingBean.users.selectedItem}">
    <f:selectItems value="#{backingBean.users.contents}" var="item" itemValue="#{item.value}" itemLabel="#{item.label}" />
</p:selectOneMenu>

Класс ListBacker:

public class ListBacker<T extends AbstractEntityBase> {
    // Contains the String representation of an Entity's ID (a.k.a.
    // primary key) and the associated Entity object
    Map<String, T> contents = new LinkedHashMap<String, T>(); // LinkedHashMap defaults to insertion-order iteration.

    // These hold values (IDs), not labels (descriptions).
    String selectedItem; // for SelectOne list
    List<String> selectedItems; // for SelectMany list


    public class ListItem {
        private String value;
        private String label;


        public ListItem(String value, String label) {
            this.value = value;
            this.label = label;
        }

        public String getValue() {
            return value;
        }

        public String getLabel() {
            return label;
        }
    }


    public ListBacker() {}

    public ListBacker(List<T> lst) {
        put(lst);
    }


    public void clear() {
        contents.clear();
        selectedItem = null;
        if(selectedItems != null) {
            selectedItems.clear();
        }
    }

    public List<ListItem> getContents() {
        return convert(contents);
    }

    public String getSelectedItem() {
        return selectedItem;
    }

    public void setSelectedItem(String selectedItem) {
        this.selectedItem = selectedItem;
    }

    public List<String> getSelectedItems() {
        return selectedItems;
    }

    public void setSelectedItems(List<String> selectedItems) {
        this.selectedItems = selectedItems;
    }

    public T getSelectedItemAsObject() {
        return convert(selectedItem);
    }

    public List<T> getSelectedItemsAsObjects() {
        return convert(selectedItems);
    }


    public void put(T newItem) {
        contents.put(newItem.toString(), newItem);
    }

    public void put(List<T> newItems) {
        for (T t : newItems) {
            put(t);
        }
    }


    // PROTECTED (UTILITY) METHODS

    protected List<ListItem> convert(Map<String, T> maps) {
        List<ListItem> lst = new ArrayList<ListItem>();
        for (Entry<String, T> e : maps.entrySet()) {
            lst.add(new ListItem(e.getKey(), e.getValue().desc()));
        }
        return lst;
    }

    protected List<T> convert(List<String> ids) {
        List<T> lst = new ArrayList<T>();
        for (String id : ids) {
            lst.add(convert(id));
        }
        return lst;
    }

    protected T convert(String id) {
        return contents.get(id);
    }

}

У меня есть две реализации toString(), одна для объектов JPA:

public abstract class AbstractEntityBase {
    @Override
    public final String toString() {
        return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString().toString());
    }
    /**
    * Return the entity's ID, whether it is a field or an embedded ID class..
    * @return ID Object
    */
    protected abstract Object getIdForToString();
}

и один для JPA EmbeddedId:

public abstract class CompositeKeyBase {
    @Override
    public final String toString() {
        return String.format("%s[id=%s]", getClass().getSimpleName(), getIdForToString());
    }

    /**
     * Supports the class's toString() method, which is required for ListBacker.
     * Compile a string of all ID fields, with this format:
     * fieldName=StringVALUE,field2=STRINGvAlUE2,...,fieldx=stringvalue <br />
     * Recommended: start with Eclipse's "generate toString()" utility and move it to getIdForToString()
     * @return a 1-to-1 String representation of the composite key
     */
    public abstract String getIdForToString();
}

Пример реализации getIdForToString() для объекта с одним полем Id:

@Override
public Object getIdForToString() {
    return userID;
}

Пример реализации getIdForToString() для EmbeddedId с двумя полями:

@Override
public String getIdForToString() {
    return "userID=" + userID + ",roleID=" + roleID;
}
person brs5tettba    schedule 25.02.2014