Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

p:autoComplete itemLabel throws "The class 'java.lang.String' does not have the property 'label'."

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 ((SelectItem)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?

like image 820
brevleq Avatar asked Oct 04 '11 20:10

brevleq


4 Answers

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'
like image 132
BalusC Avatar answered Sep 26 '22 05:09

BalusC


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

}
like image 25
Makky Avatar answered Sep 22 '22 05:09

Makky


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

  1. You could adjust the Model by changing the member groupId to CollaboratorGroup in Collaborator if it does not destroy your design. In this case, especially as CollaboratorGroup has the member Collaborator piUserId.
  2. You could just fill the p:autoComplete with List<String> groupIdList but in this case you have to find a different solution for the itemtip.

  3. 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.
  4. 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.)

like image 43
Robster Avatar answered Sep 22 '22 05:09

Robster


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.

like image 27
Kishor Prakash Avatar answered Sep 25 '22 05:09

Kishor Prakash