JSF SelectOneMenu with noSelectionOption using label as value?

16,722

Solution 1

My problem was switching to using the converter attribute of SelectOneMenu instead of using the forClass attribute of FacesConverter.

switching

@FacesConverter(value="departmentConverter")
public class DepartmentConverter extends EntityConverter {
public DepartmentConverter(){
    super(Department.class);
}
}

to

@FacesConverter(forClass=Department.class)
public class DepartmentConverter extends EntityConverter {
public DepartmentConverter(){
    super(Department.class);
}
}

causes my own converter to be used for real values while the default converter (null converter? I have not been able to find the source code for it) is used when the NoSelectionOption attribute is set to true. My theory is that setting this attribute to true sets the type of the value to null with the label as value causing it to go to a special converter always returning null or something similar. Using the converter attribute instead of forClass causes my own converter to always be used regardless of type, and I would thus have to handle the label being sent as value myself.

Solution 2

One solution that works is to simply add

try{
    return ub.find(entityClass, getKey(value));
}catch(NumberFormatException e){ // Value isn't a long and thus not an id.
    return null;
}

to the getAsObject in EntityConverter, but this feels like I am fixing the problem in the wrong place. Sending the label as value simply does not make sense, it should really send NULL.

Share:
16,722

Related videos on Youtube

Rasmus Franke
Author by

Rasmus Franke

Updated on May 14, 2022

Comments

  • Rasmus Franke
    Rasmus Franke almost 2 years

    In a "create new user" jsf page, I have a SelectOneMenu with a custom converter and a noSelectionOption selectItem like this: (irrelevant code ommited)

    NewUser.xhtml

    <h:form>
    <h:selectOneMenu value="#{newUserController.user.department}" 
                     required="true" converter="departmentConverter">
        <f:selectItem itemLabel="Select a department" noSelectionOption="true"/>
        <f:selectItems value="#{newUserController.departments}"
                       var="dep" itemLabel="#{dep.name}" itemValue="#{dep}"/>
    </h:selectOneMenu>
    <p:commandButton action="#{newUserController.saveUser}"
                     value="#{bundle.Save}"
                     ajax="false"/>
    </h:form>
    

    NewUserController.java

    @ManagedBean
    @ViewScoped
    public class NewUserController implements Serializable {
    private static final long serialVersionUID = 10L;
    
    @EJB private UserBean userBean;
    private List<Department> departments;
    private User user;
    
    public NewUserController () {
    }
    
    @PostConstruct
    public void init(){
        user = new User();
        departments = userBean.findAllDepartments();
    }
    
    public User getUser() {
        return user;
    }
    
    public void setUser(User user) {
        this.user = user;
    }
    
    public List<Department> getDepartments(){
        return departments;
    }
    
    public String saveUser() {
        // Business logic
    }
    }
    

    DepartmentConverter.java

    @FacesConverter(value="departmentConverter")
    public class DepartmentConverter extends EntityConverter {
        public DepartmentConverter(){
            super(Department.class);
        }
    }
    

    Super converter for all entities

    public class EntityConverter<E> implements Converter{
    protected Class<E> entityClass;
    
    public EntityConverter(Class<E> type) {
        entityClass = type;
    }
    
    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
        if (value == null || value.length() == 0) {
            return null;
        }
        try {
            InitialContext ic = new InitialContext();
            UserBean ub = (UserBean)ic.lookup("java:global/CompetenceRegister/UserBean");
            return ub.find(entityClass, getKey(value));
        } catch (NamingException e) {
            return null;
        }
    }
    
    Long getKey(String value) {
        Long key;
        key = Long.valueOf(value);
        return key;
    }
    
    String getStringKey(Long value) {
        StringBuilder sb = new StringBuilder();
        sb.append(value);
        return sb.toString();
    }
    
    @Override
    public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
        if (object == null) {
            return null;
        }
        if (object instanceof AbstractEntity) {
            AbstractEntity e = (AbstractEntity) object;
            return getStringKey(e.getId());
        }
        else
            throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + entityClass.getName());
    }
    

    }

    However, when i post the form with the "Select a department" option chosen, it sends the label to getAsObject in the converter instead of null, thus causing the converter to throw an exception in getKey (tries to convert a String containing an id to a Long). Setting the itemValue attribute of the selectItem to null has no effect. The items from the collection works perfectly otherwise with the converter. Does anyone have any idea of what is causing this?

    Update An interesting thing I forgot to mention; if I remove the converter attribute from the SelectOneMenu, the noSelectionAttribute works as it should, but since the default converter doesn't know how to convert my objects, the post fails on selecting a true department instead. Can this mean that the noSelectionOption=true is SUPPOSED to send its label instead and the converter is somehow expected to handle it?

    • BalusC
      BalusC over 13 years
      @Shervin: f:selectItem noSelectionOption and f:selectItems var indicates JSF2.
  • Daniel C. Sobral
    Daniel C. Sobral over 11 years
    This answer is confusing: the first paragraph indicates a change in the exact opposite direction as suggested latter.