selectOneMenu with complex objects, is a converter necessary? [duplicate]

家住魔仙堡 提交于 2019-12-23 01:12:37

问题


Is a Converter necessary for a <h:selectOneMenu> or <p:selectOneMenu> with arbitrary user-created classes as its values? I mean, is the following code supposed to work without a converter?

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

and

@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
    }

}

I have a similar code with an enum as the type (Language) and it works perfectly. But when I replace the type with a normal java class, I get a conversion error.


回答1:


You need a converter here, as JSF will assume strings by default, that is the way you coded it. JSF has no idea how to convert your pseudo entities to strings and vice versa.

Some notes:

1 . Your getAsString method defines your identifier for your entities/POJOs, not what the JSF (or whatever) select gets as itemLabel.

2 . Your converter can dig into the DB for real entities using this infamous article:

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

You can also use CDI annotations with that "pattern".

3 . Your value = "bean"is redundant and the CDI scope of choice is usually @ViewScoped. However, you have to keep in mind that CDI @Named + JSF @ViewScoped isn't working together without using Seam 3 or Apache MyFaces CODI.




回答2:


You do not need a converter, if you use this little class that I wrote :-) It can back selectOne and selecteMany components. It requires that your class's toString() provides a one-to-one unique representation of your object. If you like, you could substitute a method name other than toString(), like toIDString()

To use ListBacker in your ManagedBean, use ListBacker<Type> wherever you would have used 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();
}

In your xhtml file:

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

The ListBacker class:

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);
    }

}

I have two toString() implementations, one for JPA entities:

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();
}

and one for JPA EmbeddedId's:

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();
}

An example implementation of getIdForToString(), for an entity that has one Id field:

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

An example implementation of getIdForToString(), for an EmbeddedId that has two fields:

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


来源:https://stackoverflow.com/questions/14892680/selectonemenu-with-complex-objects-is-a-converter-necessary

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!