'Primefaces autoComplete component with different value type for suggestions and component value

I'm not sure if this is possible ... but I'd like to use the autoComplete component, where the value attribute is of type String and where the completeMethod returns a List of some heavy object.

It is also a requirement for me to use forceSelection="false"

This is what I think should work (but doesn't):

        <p:autoComplete id="it_demandeur"
                        value="#{utilisateurDemandeurCtrl.critereRechercheDemandeur}"
                        var="demandeurItem"
                        itemLabel="#{demandeurItem ne null ? demandeurItem.numeroOW.toString().concat(' - ').concat(demandeurItem.nom) : ''}"
                        itemValue="#{demandeurItem.nom}"
                        completeMethod="#{utilisateurDemandeurCtrl.completeDemandeur}" 
                        minQueryLength="3"
                        cacheTimeout="10000">

           <p:column>
              #{demandeurItem.numeroOW} - #{demandeurItem.nom}

           </p:column>


        </p:autoComplete>

This is the method that returns the list of suggestions:

@SuppressWarnings("unchecked")
   public List<Demandeur> completeDemandeur(String query) {

  StringBuilder jpql = new StringBuilder(128);

  jpql.append("SELECT d");
  jpql.append(" FROM Demandeur d");
  jpql.append(" WHERE UPPER(d.nom) LIKE :query");
  jpql.append(" OR d.numeroOW LIKE :query");

  Query demandeurQuery = em.createQuery(jpql.toString());

  demandeurQuery.setParameter("query", "%" + query.toUpperCase() + "%");

  return (List<Demandeur>) demandeurQuery.getResultList();
 }

If the user selects a suggestion, it would set the itemValue to the name of the selected suggestion, but display a concatenated string of 2 values from the Demandeur object.

The suggestions do appear and I can select them, but unfortunely, I get this error when submitting the page:

Exception message: /page/utilisateur.xhtml at line 27 and column 50 itemLabel="#{demandeurItem ne null ? demandeurItem.numeroOW.toString().concat(' - ').concat(demandeurItem.nom) : ''}": Property 'numeroOW' not found on type java.lang.String

My understanding is that the var attribute of the autoComplete component is an iterator on the suggestions, so in my case of type Demandeur, not String.

Any help would be appreciated!

Thanks

I am using primefaces 3.5.11, MyFaces implementation for JSF on Websphere 8.5.5.0

Edit:

Here is the converter I tried with

@FacesConverter(value = "demandeurUIConverter")
public class DemandeurConverter implements Serializable, Converter {

   private static final long serialVersionUID = 1L;

   @Override
   public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) throws ConverterException {
      if (value == null || value.length() == 0) {
         return null;
      }
      ConverterCtrl cc = EJB.lookup(ConverterCtrl.class);
      Demandeur d = cc.getDemandeurFromCle(value);
      if (d == null) {
         d = new Demandeur();
         d.setNom(value);
         d.setNumeroOW(value);
      }
      return d;
   }

   @Override
   public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) throws ConverterException {
      if (value == null) {
         return "";
      }
      Demandeur demandeur = (Demandeur) value;
      return demandeur.getNom();
   }

}


Solution 1:[1]

This will work if you make a converter for Demandeur.

Solution 2:[2]

Actually, the problem seems to be this part

itemValue="#{demandeurItem.nom}"

if the itemValue is the name of your item, then the converter will try to convert from a String and not from the object. So your converter method below will receive "value" = string, not a Demandeur as you may expect.

public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) throws ConverterException

I've tried this code below and I think it's what you need. I am using primefaces 4.0 on tomee 1.6.0.

the converter

import java.io.Serializable;

import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

@ManagedBean
@RequestScoped
public class DemandeurConverter implements Converter, Serializable {

    private static final long   serialVersionUID    = 1L;

    @EJB
    Demandeurs                  ejb;

    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) throws ConverterException {
        if (value == null || value.length() == 0) {
            return null;
        } else {
            return ejb.getData().get(Long.parseLong(value));
        }
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) throws ConverterException {
        System.out.println(value.getClass());
        if (value == null) {
            return null;
        } else {
            return ((Demandeur) value).getId().toString();
        }
    }
}

the managed bean

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class UtilisateurDemandeurCtrl implements Serializable {

    private static final long   serialVersionUID    = -3027573774106311465L;

    @EJB 
    private Demandeurs                  ejb;

    private Demandeur critereRechercheDemandeur;

    public List<Demandeur> completeDemandeur(String query) {
        List<Demandeur> l = new ArrayList<Demandeur>();

        for(Entry<Long, Demandeur> entryset:ejb.getData().entrySet()){
            if (entryset.getValue().getNom().contains(query)){
                l.add(entryset.getValue());
            }
        }

        return l;
    }

    public Demandeur getCritereRechercheDemandeur() {
        return critereRechercheDemandeur;
    }

    public void setCritereRechercheDemandeur(Demandeur critereRechercheDemandeur) {
        this.critereRechercheDemandeur = critereRechercheDemandeur;
    }
}

the EJB

import java.util.HashMap;
import java.util.Map;

import javax.ejb.Singleton;


@Singleton
public class Demandeurs {

    private static final Map<Long,Demandeur> data = new HashMap<Long,Demandeur>(){
        private static final long   serialVersionUID    = -4394378761837292672L;

        {
             put(1L,new Demandeur(1L,"ooooooooooone",111));
             put(2L,new Demandeur(2L,"ttttttttttttwo",222));
        }
    };

    public static Map<Long, Demandeur> getData() {
        return data;
    }


}

the entity bean

import java.io.Serializable;


public class Demandeur implements Serializable{

    private static final long   serialVersionUID    = 4023658749746098762L;
    private Long id;
    private String nom;
    private Integer numeroOW;
    public Demandeur() {}

    public Demandeur(Long id, String nom, Integer numeroOW) {
        super();
        this.id = id;
        this.nom = nom;
        this.numeroOW = numeroOW;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getNom() {
        return nom;
    }
    public void setNom(String nom) {
        this.nom = nom;
    }
    public Integer getNumeroOW() {
        return numeroOW;
    }
    public void setNumeroOW(Integer numeroOW) {
        this.numeroOW = numeroOW;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((nom == null) ? 0 : nom.hashCode());
        result = prime * result + ((numeroOW == null) ? 0 : numeroOW.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Demandeur other = (Demandeur) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (nom == null) {
            if (other.nom != null)
                return false;
        } else if (!nom.equals(other.nom))
            return false;
        if (numeroOW == null) {
            if (other.numeroOW != null)
                return false;
        } else if (!numeroOW.equals(other.numeroOW))
            return false;
        return true;
    }

}

and the xhtml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui">
<h:head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Test</title>
    <h:outputScript library="js" name="common.js" />
</h:head>
<h:body>

    <h:form id="somePrefix">

        <p:autoComplete 
            id="it_demandeur"
            value="#{utilisateurDemandeurCtrl.critereRechercheDemandeur}"
            var="demandeurItem"
            converter="#{demandeurConverter}"
            itemLabel="#{demandeurItem ne null ? demandeurItem.numeroOW.toString().concat(' - ').concat(demandeurItem.nom) : ''}"
            itemValue="#{demandeurItem}"
            forceSelection="true"
            completeMethod="#{utilisateurDemandeurCtrl.completeDemandeur}">

            <p:column>
              #{demandeurItem.numeroOW} - #{demandeurItem.nom}
            </p:column>
        </p:autoComplete>

    </h:form>
</h:body>
</html>

Solution 3:[3]

I am having the same exact issue.

This gives me an error: itemLabel="#{user.fullName}": Property 'fullName' not found on type java.lang.String

The issue comes up only when you update the autocomplete component after a submit (to show validation messages). If you remove "update" attribute from a commandButton, it works just fine.

<p:panel id="resourceConfigNewFormPanel">
    <p:autoComplete id="newUsername" value="#{userResourceConfigurationListBean.resourceConfig.username}" completeMethod="#{userResourceConfigurationListBean.autocompleteUser}" var="user" itemLabel="#{user.fullName}" itemValue="#{user.userName}" forceSelection="true" required="true">
    </p:autoComplete>
    <p:commandButton value="..." action="..." update="resourceConfigNewForm" oncomplete="if (args &amp;&amp; !args.validationFailed) resourceConfigNewDlg.hide()" />

And this works with and without update:

<p:autoComplete id="newUsername" value="#{userResourceConfigurationListBean.resourceConfig.user}" completeMethod="#{userResourceConfigurationListBean.autocompleteUser}" var="user" itemLabel="#{user.fullName}" itemValue="#{user}" converter="#{userNameToUserConverter}" forceSelection="true" required="true">
</p:autoComplete>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 rdcrng
Solution 2
Solution 3 user2027349