I'm changing from IceFaces to PrimeFaces (I really wanted to change to RichFaces but cause a bug in new version, I won't) and I'm havinng some dificults to implement correctly primefaces autoComplete. According to his manual I just need to implement a method that returns a list of objects, and in this case a converter is required.
The list I'm returning is a list of javax.faces.model.SelectItem, I really can't understand why I need to create a converter to this, but lets continue. I've created a simple converter just to test, but primefaces don't recognizes my converter and returns this error in browser:
/resources/components/popups/popupBuscaPessoa.xhtml @35,41 itemLabel="#{pessoa.label}": The class 'java.lang.String' does not have the property 'label'.
This is my conversor class (just to test):
public class ConversorSelectItem implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value!=null && value.isEmpty())
return null;
SelectItem selectItem=new SelectItem();
selectItem.setLabel(value);
return selectItem;
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object object) {
return ((Select开发者_运维问答Item)object).getLabel();
}
}
This is where I try use p:autocomplete:
<p:autoComplete value="#{modeloPopupBuscaPessoa.itemSelecionado}"
completeMethod="#{controladorSugestaoPessoa.atualizarSugestoes}"
var="pessoa" itemLabel="#{pessoa.label}" itemValue="#{pessoa.value}"
converter="#{conversorSelectItem}"/>
Did I do something wrong? Isn't there a default converter for SelectItem? Is there a easier way to implement this converter?
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.)
One more easiest way to achieve this by:
overriding the toString() method in Pessoa Pojo class.
This toString() should only return Label that you want to display.
If you use this method then there is NO NEED for converter.
For example:
public class Pessoa implements Serializable{
private String value;
private String label;
//Setter and getters
@Override
public void toString(){
return label;
}
}
Then you can use:
<p:autoComplete value="#{modeloPopupBuscaPessoa.itemSelecionado}"
completeMethod="#{controladorSugestaoPessoa.atualizarSugestoes}"
var="pessoa" itemLabel="#{pessoa}" itemValue="#{pessoa.value}"/>
This is the way i'm currently using and working well.
ELContext elContext = FacesContext.getCurrentInstance().getELContext();
ItemBean itemBean = (ItemBean) elContext.getELResolver().getValue(elContext, null, "itemBean");
for(Item item : itemBean.getItems()){
if(item.getId().getItemCode().equals(value)){
return item;
}
}
精彩评论