p:autoComplete itemLabel throws "The class 'java.lang.String' does not have the property 'label'."
You shouldn't feed it with List<SelectItem>
. You should feed it with List<Pessoa>
. You should also not concentrate on converting SelectItem
. You should concentrate on converting the item value, which is Pessoa
. The SelectItem
is a leftover from the old JSF 1.x ages. In JSF 2.x this is not mandatory anymore, thanks to the var
, itemValue
and itemLabel
attributes in the view. This keeps your bean clean from view-specific clutter.
The Converter
is only necessary whenever you use itemValue="#{pessoa}"
and the #{modeloPopupBuscaPessoa.itemSelecionado}
refers a Pessoa
property. You should then in getAsString()
convert Pessoa
to its unique String
representation (so that it can be printed in HTML) and in getAsObject()
convert from String
to Pessoa
(so that it can be set in bean property).
However, if #{pessoa.value}
is a String
and #{modeloPopupBuscaPessoa.itemSelecionado}
is also a String
, then you should just use itemValue="#{pessoa.value}"
and remove the Converter
altogether.
See also:
- PrimeFaces showcase:
<p:autoComplete>
with POJO - Can I use omnifaces generic converter in primefaces autocomplete component?
- Conversion Error setting value for 'null Converter'
A generic Converter which you can use for Primefaces Auto-completes and all other purposes:
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.WeakHashMap;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
@FacesConverter(value = "entityConverter")
public class EntityConverter 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;
}
}
I faced the same issue and the author's comment at Primefaces autocomplete with POJO and String value gave me the hint to find the source of the problem in my case.
Overview
The problem is that value=#{objectValue}
is of type String
but the method referenced in completeMethod
is returning a List<Object>
.
The Design
I have the following POJOs (simplified):
public class CollaboratorGroup {
private String groupId;
private String groupName;
private Collaborator piUserId;
...
}
and
public class Collaborator {
private String userId;
private String fullName;
private String groupId;
...
}
Does not matter whether this is a useful design. I just want to address the issue.
The following p:autoComplete
(simplified):
<p:autoComplete var="group"
itemLabel="#{group.groupId}"
itemValue="#{group.groupId}"
completeMethod="#{bean.completeGroup}"
value="#{collaborator.groupId}">
<f:facet name="itemtip">
<p:panelGrid columns="2">
<f:facet name="header">
<h:outputText value="#{group.groupId}" />
</f:facet>
<h:outputText value="Name:" />
<h:outputText value="#{group.groupName}" />
<h:outputText value="PI" />
<h:outputText value="#{group.piUserId.fullName}" />
</p:panelGrid>
</f:facet>
</p:autoComplete>
will throw The class 'java.lang.String' does not have the property 'groupId'
. When I change to itemLabel=#{group}
, I will see the groupId CG00255
in the input field but many of org.coadd.sharedresources.model.CollaboratorGroup@...
in the dropdown list. If I select one of these, this toString()
value is set to Collaborator.groupId which is not desired.
The Problem's Source
I feed the p:autoComplete
with a List<CollaboratorGroup>
while Collaborator.groupId
is a String
and itemLabel
is used to "format" both, the String groupId
set as value="#{collaborator.groupId}"
and the CollaboratorGroup
that comes from the List
, generated by completeMethod="#{bean.completeGroup}"
.
Possible Solutions
- You could adjust the
Model
by changing the membergroupId
toCollaboratorGroup
inCollaborator
if it does not destroy your design. In this case, especially asCollaboratorGroup
has the memberCollaborator piUserId
. You could just fill the
p:autoComplete
withList<String> groupIdList
but in this case you have to find a different solution for theitemtip
.A very quick solution is to use
itemLabel="#{group.class.simpleName eq 'String' ? group : group.groupId}"
as mentionned at Primefaces autocomplete with POJO and String value.- Problems
- You have to care about
NullPointerExceptions
. - You fill up your
View
with logic. - It is not a very flexible or dynamic design.
- You have to care about
- Problems
Implement 3. in a bean method
itemLabel="#{bean.printGroupId(group)}"
where you have full control over the logic. This is what I did.public String printGroupId(Object group) { if (group == null) return null; return (group instanceof String) ? (String) group : (group instanceof CollaboratorGroup) ? ((CollaboratorGroup) group).getGroupId() : null; }
(Not the best, just to give you an idea.)