I want to dynamically create a dropdown (HtmlSelectOneMenu) with all possible options. The option currently set should be preselected. To achieve this I created a value expression for my form:
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
menu.setValueExpression("value", valueExpression);
The string #{%s.item.%s} evaluates to #{playlistController.item.category} and category is the object of type Category that I want to bind to my dropdown.
To use the SelectItemsConverter from OmniFaces I changed the toString() method of Category to:
#Override
public String toString() {
return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
}
My code for the form generation looks like this (Note: Category extends BaseEntity):
private UIComponent createDropdown(FormInput property) {
String key = property.getKey();
String beanName = key + "Controller";
GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);
List<BaseEntity> list = controller.getService().findAll();
List<SelectItem> selectItems = new ArrayList<>(list.size());
for (BaseEntity itemInList : list) {
selectItems.add(new SelectItem(itemInList, itemInList.getName()));
}
UISelectItems items = new UISelectItems();
items.setValue(selectItems.toArray());
HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
menu.setConverter(new SelectItemsConverter());
menu.setId(key);
menu.getChildren().add(items);
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
menu.setValueExpression("value", valueExpression);
return menu;
}
If I set menu.setConverter(new SelectItemsConverter()); then the wrong item is preselected. But If I remove it, then the correct item is selected but when I try to save the form it fails because it has no converter for the dropdown.
Can anyone help me? I've published the code on GitHub.
The method createDropdown can be found at the bottom of the linked code.
Your mistake is here, in the String.class type argument:
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
A Category is not a String. Use Category.class instead and everything should be well. At least, theoretically. I can't copy'n'paste'n'run this piece of code without changes.
I found the error in the code. Simply the equals() and hashCode() of the BaseEntity were insufficient. And the error BalusC already mentioned. The resulting code looks like the following:
GenFormBaseController.java
private UIComponent createDropdown(FormInput property) {
String key = property.getKey();
Class<?> expectedType = property.getValue();
String beanName = lowerFirstChar(expectedType.getSimpleName()) + "Controller";
GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);
List<BaseEntity> list = controller.getService().findAll();
List<SelectItem> selectItems = new ArrayList<>(list.size());
for (BaseEntity itemInList : list) {
selectItems.add(new SelectItem(itemInList, itemInList.getName()));
}
UISelectItems items = new UISelectItems();
items.setValue(selectItems);
HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
menu.setConverter(new SelectItemsConverter());
menu.setId(key);
menu.getChildren().add(items);
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, expectedType);
menu.setValueExpression("value", valueExpression);
return menu;
}
BaseEntity.java
#Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.id);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BaseEntity other = (BaseEntity) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return true;
}
Related
This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 6 years ago.
one more time i'm in trouble here. My point is:
In my project i need a converter for (obviously) convert the items from the SelectOneMenu component to a list property in the respective bean. In my jsf page i have:
<p:selectOneMenu id="ddlPublicType" value="#{publicBean.selectedPublicType}" effect="fade" converter="#{publicBean.conversor}" >
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt" itemLabel="#{pt.label}" itemValue="#{pt.value}"></f:selectItems>
</p:selectOneMenu>
And my bean is:
#ManagedBean(name = "publicBean")
#RequestScoped
public class PublicBean {
// Campos
private String name; // Nome do evento
private TdPublicType selectedPublicType = null;
private List<SelectItem> lstPublicTypes = null;
private static PublicTypeDAO publicTypeDao; // DAO
static {
publicTypeDao = new PublicTypeDAO();
}
// Construtor
public PublicoBean() {
lstPublicTypes = new ArrayList<SelectItem>();
List<TdPublicType> lst = publicTypeDao.consultarTodos();
ListIterator<TdPublicType> i = lst.listIterator();
lst.add(new SelectItem("-1","Select..."));
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstPublicTypes.add(new SelectItem(actual.getIdPublicType(), actual.getNamePublicType()));
}
}
// Getters e Setters
...
public Converter getConversor() {
return new Converter() {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// This value parameter seems to be the value i had passed into SelectItem constructor
TdPublicType publicType = null; // Retrieving the PublicType from Database based on ID in value parameter
try {
if (value.compareTo("-1") == 0 || value == null) {
return null;
}
publicType = publicTypeDao.findById(Integer.parseInt(value));
} catch (Exception e) {
FacesMessage msg = new FacesMessage("Error in data conversion.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext.getCurrentInstance().addMessage("info", msg);
}
return publicType;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString(); // The value parameter is a TdPublicType object ?
}
};
}
...
}
In the getAsObject() method, the value parameter seems to be the value i had passed into SelectItem constructor. But in the getAsString() method, the value also seems to be a string representation of an Id. This parameter shouldn't be of type TdPublicType ? There is anything wrong in my code?
The getAsString() should convert the Object (which is in your case of type TdPublicType) to a String which uniquely identifies the instance, e.g. some ID, so that it can be inlined in HTML code and passed around as HTTP request parameters. The getAsObject() should convert exactly that unique String representation back to the concrete Object instance, so that the submitted HTTP request parameter can be converted back to the original object instance.
Basically (trivial prechecks and exception handling omitted):
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Convert Object to unique String representation for display.
return String.valueOf(((TdPublicType) modelValue).getId());
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) throws ConverterException {
// Convert submitted unique String representation back to Object.
return tdPublicTypeService.find(Long.valueOf(submittedValue));
}
Update: you've another problem, you're specifying the value property of TdPublicType class as the item value instead of the TdPublicType instance itself. This way the converter will retrieve the value property instead of the TdPublicType instance in the getAsString(). Fix it accordingly:
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt"
itemLabel="#{pt.label}" itemValue="#{pt}"/>
Now the code is working. My error was in the loading method. I was doing this:
// Loading menu
List<TdPublicType> l = daoPublicType.retrieveAll();
Iterator<TdPublicType> i = l.iterator();
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstMenuPublicType.add(new SelectItem(actual.getIdtPublicType(), actual.getNamePublicType()));
}
But the right way is:
// Loading menu
List<TdPublicType> l = daoPublicType.retrieveAll();
Iterator<TdPublicType> i = l.iterator();
while (i.hasNext()) {
TdPublicType actual = (TdPublicType) i.next();
lstMenuPublicType.add(new SelectItem(actual, actual.getNamePublicType())); // In the first parameter i passed the PublicType object itself not his id.
}
use can use generic converter which will convert the value in the backing bean.
You do not need any casting also.
#FacesConverter(value = "GConverter")
public class GConverter implements Converter{
private static Map<Object, String> entities = new WeakHashMap<Object, String>();
#Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String uuid) {
for (Entry<Object, String> entry : entities.entrySet()) {
if (entry.getValue().equals(uuid)) {
return entry.getKey();
}
}
return null;
}
}
Example usage would be
<p:selectOneMenu id="ddlPublicType" value="#{publicBean.selectedPublicType}" effect="fade" converter="GConverter" >
<f:selectItems value="#{publicoBean.lstPublicTypes}" var="pt" itemLabel="#{pt.label}" itemValue="#{pt}"></f:selectItems>
</p:selectOneMenu>
Made a post earlier about this topic (Display values in drill-down SelectOneMenus). The problem that have is not connected to the Object converters as I first thought.
I have two selectOneMenu depending on each other, Sector -> Category. When I persist the background business object that holds a Sector and Category, everything works as expected. Even after the current user session terminates and the user log on again and edit the previous saved business object.
The problem occur when and the entity manager flushes the objects and user wants to display the Sector, Category again from the database. The Sector selectOneMenu displays its value as it should, but the Category selectOneMenu value is not displayed. Although the backing bean has the correct value from the database.
What makes the selectOneMenuto not display certain persisted values/objects when they are loaded from database instead from in-memory?
Edit.xhtml
<h:outputLabel value="Sector:" />
<h:selectOneMenu id="sectorSelector" value="#{activityController.selected.category.sector}" title="#{bundle.CreateSectorLabel_sectorName}" valueChangeListener="#{activityController.changeSectorMenu}" immediate="true">
<a4j:ajax event="change" execute="#this categoryMenu" render="categoryMenu"/>
<f:selectItems value="#{sectorController.itemsAvailableSelectOne}"/>
</h:selectOneMenu>
<h:outputLabel value="Category:" />
<h:selectOneMenu id="categoryMenu" value="#{activityController.selected.category}" title="#{bundle.CreateSectorLabel_sectorName}"
binding="#{activityController.categoryMenu}"
required="true" requiredMessage="#{bundle.CreateCategoryRequiredMessage_sector}">
<f:selectItems value="#{activityController.categorySelection}"/>
</h:selectOneMenu>
Controller bean for Category
#ManagedBean(name = "categoryController")
#SessionScoped
public class CategoryController implements Serializable{
....
#FacesConverter(forClass = Category.class)
public static class CategoryControllerConverter implements Converter {
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
CategoryController controller = (CategoryController) facesContext.getApplication().getELResolver().
getValue(facesContext.getELContext(), null, "categoryController");
return controller.ejbFacade.find(getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + CategoryController.class.getName());
}
}
}
Part of POJO object
...
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idCategory")
private Integer idCategory;
...
#Override
public boolean equals(Object object) {
if (!(object instanceof Category)) {
return false;
}
Category other = (Category) object;
if ((this.idCategory == null && other.idCategory != null) || (this.idCategory != null && !this.idCategory.equals(other.idCategory))) {
return false;
}
return true;
}
Functon for creating the SelectItem[] array
public static SelectItem[] getSelectItems(List<?> entities, boolean selectOne) {
int size = entities.size();
SelectItem[] items;
int i = 0;
if (selectOne) {
items = new SelectItem[size + 1];
items[0] = new SelectItem("", ResourceBundle.getBundle("/resources/Bundle").getString("Select_item"));
i++;
} else {
items = new SelectItem[size];
}
for (Object x : entities) {
items[i++] = new SelectItem(x, x.toString());
}
return items;
}
The problem was that I didn't have the right collection of SelectItems when the user edited a business object. Plus that I added the <f:converter> to explicitly invoke the converter, where I did some changes in the getAsString() (the else part).
Thank you for your time, and hope the post can be useful to someone else.
SelectMenuOne
public String prepareEditOrganisationActivity() {
current = (Activity) organisationActivityItems.getRowData();
Sector sector = current.getCategory().getSector();
CategoryController categoryBean = JsfUtil.findBean("categoryController", CategoryController.class);
categorySelection = categoryBean.categoryItemsBySector(sector);
return "EditActivity";
}
CategoryControllerConverter
...
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
Category tmp = new Category();
return getStringKey(tmp.getIdCategory());
}
}
Of five options in a selectOneMenu I chose the second option and persisted the entity.
On edit the persisted entity selectOneMenu always has the last option as its value.
For example,
<h:selectOneMenu value="#{userHome.user.leader}">
<f:selectItems value="#{userHome.availableLeaders}" var="leader" itemLabel="# {leader.name}" itemValue="#{leader}"/>
</h:selectOneMenu>
where availableLeaders is a list of users populated #PostConstruct method.
I am expecting the selectOneMenu to have the second option(chosen) on edit.
#FacesConverter(forClass = User.class, value = "userConverter")
public class UserConverter implements Converter {
public UserConverter() {
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("DefaultPersistenceUnit");
EntityManager em = emf.createEntityManager();
Query q = em.createQuery("select query");
return q.resultList().get(0);
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((User) value).getName();
}}
In User.java
public boolean equals(Object other) {
if (this.getClass().isInstance(other)) {
return true;
} else {
return false;
}
}
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(getId());
builder.append(getName());
return builder.toHashCode();
}
Look here:
public boolean equals(Object other) {
if (this.getClass().isInstance(other)) {
return true;
} else {
return false;
}
}
Your equals() method is definitely broken. This returns true for every other User object, even though it internally holds a completely different user ID/name. So the selected item matches every available select item value. That's why you see the last item being preselected everytime.
Assuming that the id property is unique for every user, then the equals() method should at its simplest rather look like this:
public boolean equals(Object other) {
if (!(other instanceof User)) {
return false;
}
if (other == this) {
return true;
}
if (id != null) {
return id.equals(((User) other).id);
}
return false;
}
which can also be summarized as follows
public boolean equals(Object other) {
return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this);
}
Hint: a bit decent IDE like Eclipse can autogenerate equals() (and hashCode()) method.
See also:
Right way to implement equals contract
Generic reflective helper method for equals and hashCode
I am trying to set default value to h:selectOneMenu. But, It is not working.
This is my code
index.xhtml
<h:body>
<h:form id="test">
<h:selectOneMenu value="#{selectMenuBean.selectedItem}"
title="select version"
onchange="submit()"
disabled="false" id="combo">
<f:selectItems value="#{selectMenuBean.selectItems}" />
</h:selectOneMenu>
</h:form>
</h:body>
BackingBean
private String selectedItem;
private List selectItems;
private int version=3;
public List getSelectItems() {
List<Version> selectedItems = ExportDao.getVersionsList();
System.out.println("List size: "+selectedItems.size());
selectItems = new ArrayList();
for (Version v1 : selectedItems) {
String DATE_FORMAT = "yyyy-MM-dd HH:mm";
//Create object of SimpleDateFormat and pass the desired date format.
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
selectItems.add(new SelectItem(v1.getVersion(), "V" + v1.getVersion() + "/" + sdf.format(v1.getDate())));
if(version = v1.getVersion()) // I have to check the version and set the matching version as selected.
selectedItem = "V" + v1.getVersion() + "/" + sdf.format(v1.getDate());
}
return selectItems;
}
You're setting the selectedItem with the item label instead of the item value.
Replace
selectedItem = "V" + v1.getVersion() + "/" + sdf.format(v1.getDate());
by
selectedItem = v1.getVersion();
A few possible solutions:
1) Set the type of selectItems to SelectItem[] instead of and untyped List.
or 2) Try setting the var, itemValue and itemLabel attributes of the selectItems like below, and put actual Version objects in your list.
or my favorite, 3) Make a VersionConverter that knows how to convert a Version object from and to a String. Example below if your Version object is persisted in a database and has an Id. After this is constructed, your selectedItem and List selectItems should have the typ Version (and List), not String. JSF will handle the conversion by itself.
#FacesConverter(forClass=Version.class)
public class VersionConverter implements Converter{
public VersionConverter() {
}
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
try {
// Get an EJB that can fetch the Version from a DB. Alternativly, do whatever you need to get your object from a string.
InitialContext ic = new InitialContext();
MyDao myDao = (MyDao)ic.lookup(String.format("java:global/%s/MyBean", (String)ic.lookup("java:module/ModuleName")));
return myDao.findEntity(Version.class, 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 Version) {
Version e = (Version) object;
return getStringKey(e.getId());
}
else
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + Version.class.getName());
}
}
Hi I've this situation and I dont know why it's happening..
I have a selectonemenu like this
<ice:selectOneMenu id="ddlProfesion" value="#{FrmClientes.profesionSeleccionado}" style="width:230px">
<f:selectItems value="#{SessionBean1.listaProfesion}"/>
<f:converter converterId="DefaultSelectItemConverter" />
</ice:selectOneMenu>
the list of items
public List getListaProfesion() {
if (listaProfesion == null) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
listaProfesion = new ArrayList<SelectItem>();
List<Profesion> profesionList = session.getNamedQuery("Profesion.findAll").list();
for (Profesion c : profesionList) {
listaProfesion.add(new SelectItem(c, c.getNombre()));
}
return listaProfesion;
}
return listaProfesion;
}
now I have a datatable and when I click in a row a panelPopup open with the data of the object Profesion..
the code of the selectionListener in the rowSelector is this:
public void seleccionaTerceros(RowSelectorEvent event) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Query query = session.getNamedQuery("Clientes.findByTercero");
query.setParameter("tercero", "12332454");
// I send a parameter value for example
if (!query.list().isEmpty()) {
cliente = (Clientes) query.list().get(0);
profesionSeleccionado=cliente.getProfesionID();
} else {
cliente = null;
profesionSeleccionado=null;
}
setMostrarModal(true);
}
I set profesionSeleccionado to the Value of the objetc and doesnt work, I put this code in another location, like the constructor of the managed bean or in a button action.. and IT WORKS...
I have debbuged and see that the getter and the setter of the atribute are accesed twice, why, i dont know
please I need some guide, I'am new with this.. Thanks
pd: the code of the converter used to list objects in the selectonemenu is this
public class DefaultSelectItemConverter implements Converter {
/**
* Not explicitly documented.
*
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.String)
*/
public Object getAsObject(FacesContext fcontext, UIComponent comp, String valueString) {
List<UIComponent> children = comp.getChildren();
for (UIComponent child : children) {
if (child instanceof UISelectItem) {
UISelectItem si = (UISelectItem) child;
if (si.getValue().toString().equals(valueString)) {
return si.getValue();
}
}
if (child instanceof UISelectItems) {
UISelectItems sis = (UISelectItems)child;
List<SelectItem> items = (List)sis.getValue();
for (SelectItem si : items) {
if (si.getValue().toString().equals(valueString)) {
return si.getValue();
}
}
}
}
throw new ConverterException("no conversion possible for string representation: " + valueString);
}
/**
* Not explicitly documented.
*
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.Object)
*/
public String getAsString(FacesContext fcontext, UIComponent comp, Object value) {
return value.toString();
}
}
I found the solution in this page
http://jira.icefaces.org/browse/ICE-2297
the problem was corrected putting immediate="false" on the RowSelector
:)